QxOrm Windows Linux Macintosh C++

Home Download Quick sample Tutorial (4)
Manual (2)
Forum Our customers

QxOrm >> Manual - QxOrm library user guide
Current version :  QxOrm 1.5.0 - QxOrm library online class documentation - GitHub
QxEntityEditor 1.2.8
Version française du site Web site english version
Select a manual : QxOrm user guide QxEntityEditor user guide


QxOrm library manual - Table of Contents

  1. Introduction
    1. QxOrm library
    2. Quick overview of QxEntityEditor application
    3. C++ coding style of QxOrm library
  2. Installation
    1. Qt dependency
    2. Boost dependency (optional)
    3. QxOrm.pri (or QxOrm.cmake) configuration file
    4. Build QxOrm library (with qmake or CMake)
    5. Qt SQL drivers (supported databases)
  3. Persistence - Object Relational Mapping (ORM)
    1. Register a class in QxOrm context (mapping)
      1. Primary key other than default type "long"
      2. Composite primary key
      3. public or protected/private data members
      4. Namespace
      5. Built-in C++ supported types
      6. Register a transient data member
    2. Connection to database
    3. Persist a C++ instance in database (insert/update)
    4. Delete an instance in database (delete)
      1. Soft delete behaviour (logical delete)
    5. Get a C++ instance from database (fetch)
    6. SQL queries
      1. Using qx::QxSqlQuery class (or qx_query alias)
      2. Using standard SQL or stored procedure
    7. Transactions (commit, rollback, session)
    8. Relationships
      1. one-to-many (1-n)
      2. many-to-one (n-1)
      3. many-to-many (n-n)
      4. one-to-one (1-1)
      5. Fetch relationships
      6. Select columns fetching relationships and define custom SQL alias
      7. Add SQL query inside LEFT OUTER JOIN / INNER JOIN
    9. Supported containers
      1. Qt containers
      2. Boost containers
      3. std containers
      4. qx::QxCollection
    10. Supported smart pointers
      1. Qt smart pointers
      2. Boost smart pointers
      3. std smart pointers
      4. qx::dao::ptr
    11. Triggers
    12. Validators
    13. Manage NULL database value
      1. boost::optional
      2. QVariant
    14. Inheritance and polymorphism
    15. qx::IxPersistable interface (abstract class)
    16. Use PIMPL C++ pattern (Private Implementation idiom or d-pointer)
    17. Persist custom type
    18. Generate database DDL SQL schema
    19. Associate a SQL type to a C++ class
    20. Async database queries
    21. Cache to store C++ instances (QxCache module)
    22. Working with several databases
    23. Register an abstract class in QxOrm context
    24. Register automatically Qt meta-properties (Q_PROPERTY macro)
  4. Serialization
    1. Version number to manage ascendant compatibility
    2. Qt QDataStream engine
    3. Qt JSON engine
    4. XML boost serialization
    5. Binary boost serialization
    6. Other boost serialization
    7. Clone a C++ instance
    8. Dump a C++ instance (XML or JSON format)
  5. Introspection - Reflection
    1. Get a data member value dynamically
    2. Set a data member value dynamically
    3. Call function dynamically
    4. Create a C++ instance dynamically
    5. Iterate over all classes/properties registered in QxOrm context
  6. Services : transfer persistent data layer over network (QxService module)
    1. Input/output service parameters (request/response)
    2. Define service functions exposed to clients
    3. List of options available on server side
    4. Connection settings on client side
    5. Service authentication
    6. Async client/server queries
  7. Model View engine (QxModelView module)
    1. Simple model (without relationship)
    2. Model with relationships (nested models)
    3. Interaction with QML views
    4. Interaction with QtWidget views
    5. Connect model to QxService module
  8. QxOrm and MongoDB database (C++ ODM Object Document Mapper)
    1. Prerequisites : driver libmongoc and libbson
    2. QxOrm.pri (or QxOrm.cmake) configuration file
    3. Connection to MongoDB database
    4. Register a MongoDB persistent class (Collection) in QxOrm context (mapping)
      1. Manage ObjectId (primary key)
    5. Insert a C++ instance (Document) in MongoDB database
      1. Insert many C++ instances (list of Documents) in MongoDB database
    6. Update a C++ instance (Document) in MongoDB database
      1. Update many C++ instances (list of Documents) in MongoDB database
    7. Delete a C++ instance (Document) from MongoDB database
      1. Delete many C++ instances (list of Documents) from MongoDB database
    8. Fetch a C++ instance (Document) from MongoDB database
      1. Fetch many C++ instances (list of Documents) from MongoDB database
    9. JSON queries
      1. Using qx::QxSqlQuery class (or qx_query alias)
      2. Using MongoDB aggregation framework
      3. Add 'sort', 'limit', 'skip', etc..., properties to JSON query
      4. Execute a custom query
    10. Relationships engine (MongoDB version 3.6 or + is required)
      1. Embedded vs Referenced
    11. Create automatically indexes
  9. HTTP/HTTPS web server (QxHttpServer module)
    1. Hello World !
    2. HTTP/HTTPS web server settings
      1. Secured connections SSL/TLS
    3. Routing URL (dispatcher / endpoints)
      1. Dynamic URL routing
    4. Get HTTP request parameters
    5. Build HTTP response
    6. Sessions (storage per client on server side)
    7. Cookies
    8. Static files
    9. Chunked responses
    10. Requests using JSON API (QxRestApi module)
    11. WebSocket
    12. Performance (tested with Apache Benchmark)
      1. Improve performance with epoll dispatcher on Linux
  10. JSON REST API (QxRestApi module)
    1. How it works
      1. Use cases
    2. qxBlogRestApi example project (QML and HTTP web server)
    3. Fetch
      1. fetch_all
      2. fetch_by_id
      3. fetch_by_query
      4. count
      5. exist
    4. Insert
    5. Update
    6. Save (insert or update)
    7. Delete
      1. delete_all / destroy_all
      2. delete_by_query / destroy_by_query
      3. delete_by_id / destroy_by_id
    8. Validate
    9. Custom SQL query or stored procedure
    10. Call C++ natives functions
    11. Meta-data (C++ classes registered into QxOrm context)
    12. Send a list of JSON requests
qt_ambassador
QxOrm library has been accepted into the Qt Ambassador Program


Introduction

The goal of this documentation is to provide a user guide to learn how to work with QxOrm library features. This manual is intended for developers and software architects who are looking for a solution to manage a persistent data layer in C++/Qt. Technical skills in C++ and databases are required to understand this document.

Note : all features described in this manual/user guide can be defined quickly and easily with QxEntityEditor application (the graphic editor for QxOrm library, data model designer and source code generator). Another documentation dedicated to QxEntityEditor application is available on QxOrm website.

Other note : this manual is based on the old FAQ of QxOrm website, you can always access to this FAQ here.

QxOrm library

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.


Based on a simple C++ setting function per class (like Hibernate XML mapping file in Java), QxOrm library provides following features :
  • Persistence : support most common databases like SQLite, MySQL, PostgreSQL, Oracle, MS SQL Server, MongoDB (with 1-1, 1-n, n-1 and n-n relationships) ;
  • Serialization : JSON, binary and XML format ;
  • Reflection (or introspection) : access dynamically to classes definitions, retrieve properties and call classes methods ;
  • HTTP web server : standalone multi-threaded HTTP 1.1 web server (support SSL/TLS, persistent connections, cookies, sessions, chunked responses, URL dispatcher/routing) ;
  • JSON API : interoperability with other technology than C++/Qt (REST web services, QML applications, scripting language).
QxOrm depends on Qt (from version 4.5.0) and boost (from version 1.38, and by default just header files *.hpp are necessary).
QxOrm library has been accepted into the Qt Ambassador Program.

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 library is available here.
If you speak/understand french, you can access to the french community on the famous developpez.com forum.

Quick overview of QxEntityEditor application

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 generates native code for all environments : desktop (Windows, Linux, Mac OS X), embedded and mobile (Android, iOS, Windows Phone, Raspberry Pi, etc.).
A presentation video of QxEntityEditor application is available on YouTube.

QxEntityEditor is based on plugins and provides many ways to import/export your data model :
  • generate C++ persistent classes automatically (registered in 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.
A manual dedicated to QxEntityEditor application is available on QxOrm website.

C++ coding style of QxOrm library

QxOrm library uses following syntax and naming convention for C++ source code :
  • all classes, functions, properties, etc... are defined under namespace qx ;
  • all macros of QxOrm library are prefixed by QX_... ;
  • all abstracts classes (or interfaces) start with prefix Ix (for example IxFactory is an interface to create an instance of object) ;
  • other classes start with prefix Qx (for example QxDataMember) ;
  • containers of objects end with suffix X (for example QxDataMemberX is a list of QxDataMember) ;
  • functions to interact with databases are under namespace qx::dao (for example qx::dao::fetch_by_id()) ;
  • functions to serialize are under namespace qx::serialization (for example qx::serialization::xml::to_file()) ;
  • the reflection (or introspection) engine can be used with qx::QxClassX class (for example qx::QxClassX::invoke() to call a class method) ;
  • all traits classes are under namespace qx::trait (for example qx::trait::is_smart_ptr<T>).

Installation

QxOrm library is multi-platform and can be installed on all environments : Windows, Linux, Mac OS X, Android, iOS, Windows Phone, Raspberry Pi, etc...
A full tutorial (with screenshots) to install a development environment with QxOrm on Windows is available here.

The goal of this chapter is to explain all steps required to install QxOrm library on all environments :

Qt dependency

Qt Qt : cross-platform application development framework : GUI (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 many 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://www.qt.io/

Note : by default, QxOrm library depends only on QtCore and QtSql binaries. It is possible to enable extra features in the QxOrm.pri (or QxOrm.cmake) configuration file : some features can add dependencies to QxOrm library.

Boost dependency (optional)

By default, QxOrm library depends only on Qt (QtCore and QtSql). boost installation is optional and not required with default configuration.
Note : QxOrm provides 2 dependency levels on boost :
  • dependency only on boost headers files (*.hpp) : _QX_ENABLE_BOOST compilation option ;
  • dependency on boost serialization module : _QX_ENABLE_BOOST_SERIALIZATION compilation option.

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 boost's features (header files *.hpp only, boost serialization dependency is optional) : smart_pointer, type_traits, multi_index_container, unordered_container, any, tuple, foreach, function.
It is recommended to get the latest version of boost available at the following address : http://www.boost.org/

Important note : with _QX_ENABLE_BOOST compilation option, QxOrm library depends only on *.hpp boost header files (header only libraries). So in this case, boost installation is very easy because you just have to unzip boost package (to get *.hpp header files, there is nothing to build).

QxOrm.pri (or QxOrm.cmake) configuration file

QxOrm.pri (or QxOrm.cmake) configuration file is divided into several sections (each section is commented) and provides all settings and compilation options available to customize QxOrm library features. So it is strongly recommended to read carefully QxOrm.pri configuration file before compiling and building QxOrm library. It is possible to keep default settings, only QX_BOOST_INCLUDE_PATH variable is required if you work with boost : this variable is used to define where *.hpp boost header files are located :

   isEmpty(QX_BOOST_INCLUDE_PATH) { QX_BOOST_INCLUDE_PATH = $$quote(D:/Dvlp/_Libs/Boost/1_57/include) }   

If you don't want to change QxOrm.pri configuration file, it is possible to define an environment variable named BOOST_INCLUDE : this environment variable will be used automatically to set QX_BOOST_INCLUDE_PATH value (read QxOrm.pri configuration file for more details).

Here is a list of compilation options available in QxOrm.pri configuration file, by default they are all disabled :
  • _QX_ENABLE_BOOST : add a dependency to boost headers files (*.hpp), support some classes like : boost::shared_ptr, boost::optional, boost::container, etc... ;
  • _QX_ENABLE_BOOST_SERIALIZATION : enable boost::serialization engine. This option requires to build boost::serialization binary and adds a dependency to QxOrm library ;

  • _QX_ENABLE_QT_GUI : support serialization of QtGui types : QBrush, QColor, QFont, QImage, QMatrix, QPicture, QPixmap, QRegion. This option adds a dependency to QxOrm library (QtGui) ;
  • _QX_ENABLE_QT_NETWORK : enable QxService module to transfer persistent data layer over network (client/server application). This option adds a dependency to QxOrm library (QtNetwork) ;
  • _QX_NO_PRECOMPILED_HEADER : disable precompiled header (used to reduce compilation times of a project) : this option is required with recent versions of MinGW (because of a known compiler bug), for all other compilers it is recommended to work with precompiled header ;
  • _QX_NO_RTTI : build QxOrm library (and all projects which depend on QxOrm) without RTTI C++ type information ;
  • _QX_STATIC_BUILD : build QxOrm library in static mode (but build QxOrm as a shared library is recommended).
  • _QX_UNITY_BUILD : reduce QxOrm library compilation times using unity build concept : only one all.cpp source file to compile. It is recommended to enable this option with CMake (because doesn't support natively precompiled headers) ;
  • _QX_ENABLE_MONGODB : support MongoDB database, QxOrm library becomes an ODM (Object Document Mapper).

Note : QxOrm.pri (or QxOrm.cmake) configuration file must be included in all projects which depend on QxOrm library, just adding following line in *.pro project file :

   include(my_path_to_QxOrm_library/QxOrm.pri)   

Other note : instead of qmake, it is possible to use CMake compilation tools to configure and build QxOrm library. CMake provides a GUI tool to display and configure all available parameters :

QxOrm and CMake

Build QxOrm library (with qmake or CMake)

QxOrm library uses qmake process from Qt framework to create makefile and build the project (it is also possible to use CMake compilation tools, a CMakeLists.txt file is provided with QxOrm library).
qmake is portable and multi-platform, so it works perfectly on Windows, Linux (Unix) and Mac OS X.
To build QxOrm library, just execute following commands :

   qmake
   make debug
   make release   

On Windows, *.vcproj and *.sln files are available for Microsoft Visual C++.
*.pro files are readable by Qt Creator, and some plugins are available to interface to other C++ source code editors.
mingw_build_all_debug.bat and mingw_build_all_release.bat scripts in the directory ./tools/ can be used to quickly build 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 be used to quickly build 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 be used to quickly build QxOrm library and all tests on Mac OS X (thanks very much to Dominique Billet for the scripts).

Qt SQL drivers (supported databases)

QxOrm library uses QtSql engine of Qt framework based on a system of plugins.
A list of supported databases is available on Qt website.
The ODBC plugin (QODBC) ensures compatibility with many databases.
For optimal performances, it is recommended to work with a database specific plugin :
  • QMYSQL : MySQL ;
  • QPSQL : PostgreSQL (versions 7.3 and above) ;
  • QOCI : Oracle Call Interface Driver ;
  • QSQLITE : SQLite version 3 ;
  • QDB2 : IBM DB2 (version 7.1 and above) ;
  • QIBASE : Borland InterBase ;
  • QTDS : Sybase Adaptive Server.
Note : to connect to a Microsoft SQL Server database, it is necessary to use ODBC (QODBC plugin).

Other note : QxOrm library is able to connect to MongoDB database (C++ ODM Object Document Mapper).

Persistence - Object Relational Mapping (ORM)

QxOrm library provides a data persistence engine based on QtSql module of Qt framework. This persistence engine uses programming pattern : Object Relational Mapping (ORM).

From Wikipedia website : Object-Relational Mapping (ORM) in computer science is a programming technique for converting data between incompatible type systems in object-oriented programming languages. This creates, in effect, a "virtual object database" that can be used from within the programming language.
In object-oriented programming, data management tasks act on object-oriented (OO) objects that are almost always non-scalar values. For example, consider an address book entry that represents a single person along with zero or more phone numbers and zero or more addresses. This could be modeled in an object-oriented implementation by a "Person object" with attributes/fields to hold each data item that the entry comprises : the person's name, a list of phone numbers, and a list of addresses. The list of phone numbers would itself contain "PhoneNumber objects" and so on. The address book entry is treated as a single object by the programming language (it can be referenced by a single variable containing a pointer to the object, for instance). Various methods can be associated with the object, such as a method to return the preferred phone number, the home address, and so on.
However, many popular database products such as SQL database management systems (DBMS) can only store and manipulate scalar values such as integers and strings organized within tables. The programmer must either convert the object values into groups of simpler values for storage in the database (and convert them back upon retrieval), or only use simple scalar values within the program. Object-relational mapping is used to implement the first approach.
The heart of the problem is translating the logical representation of the objects into an atomized form that is capable of being stored in the database, while preserving the properties of the objects and their relationships so that they can be reloaded as objects when needed. If this storage and retrieval functionality is implemented, the objects are said to be persistent.

To do this link between object world and relational world, and to provide all its features, QxOrm library requires that C++ classes are registered in QxOrm context. So we will start this chapter this way : how to register a C++ class in QxOrm context ?

Note : QxOrm library is able to connect to MongoDB database (C++ ODM Object Document Mapper).

Register a class in QxOrm context (mapping)

All C++ classes can be registered in QxOrm context : there is no need to inherit from a super object, and you can write your methods and accessors without any constraint. Register a C++ class in QxOrm context means :
  • in *.h header file (containing class definition) : use QX_REGISTER_HPP(class_name, base_class, class_version) macro ;
  • in *.cpp source file (containing class implementation) : use QX_REGISTER_CPP(class_name) macro ;
  • in *.cpp source file (containing class implementation) : specialize template function : void qx::register_class<T>(qx::QxClass<T> & t).
Here is an example to show how to define a class named person with 4 properties registered in QxOrm context : id, firstName, lastName, birthDate :

* person.h file :
#ifndef _PERSON_H_
#define _PERSON_H_

class person
{
public:
   long id;
   QString firstName;
   QString lastName;
   QDateTime birthDate;

   person() : id(0) { ; }
   virtual ~person() { ; }
};

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

/* This macro is necessary to register 'person' class in QxOrm context */
/* param 1 : the current class to register => 'person' */
/* param 2 : the base class, if no base class, use the qx trait => 'qx::trait::no_base_class_defined' */
/* param 3 : the class version used by serialization engine to provide 'ascendant compatibility' */

#endif // _PERSON_H_

* person.cpp file :
#include "precompiled.h"   // Precompiled-header with '#include <QxOrm.h>' and '#include "export.h"'
#include "person.h"          // Class definition 'person'
#include <QxOrm_Impl.h>     // Automatic memory leak detection and boost serialization export macro

QX_REGISTER_CPP_MY_TEST_EXE(person)   // This macro is necessary to register 'person' class in QxOrm context

namespace qx {
template <> void register_class(QxClass<person> & t)
{
  t.setName("t_person");               // 'person' C++ class is mapped to 't_person' database table

  t.id(& person::id, "id");               // Register 'person::id' <=> primary key in your database
  t.data(& person::firstName, "first_name");      // Register 'person::firstName' property mapped to 'first_name' database column name
  t.data(& person::lastName, "last_name");  // Register 'person::lastName' property mapped to 'last_name' database column name
  t.data(& person::birthDate, "birth_date");  // Register 'person::birthDate' property mapped to 'birth_date' database column name
}}


Note : class methods qx::QxClass<T>::id() and qx::QxClass<T>::data() return an instance of type : qx::IxDataMember (which is the base class to register a data member). With this instance, it is possible to customize default behaviour of qx::IxDataMember class, like for example in the chapter : Register a transient data member.

Other note : it is also possible to register functions and class methods in QxOrm context (support static and non static methods) with qx::QxClass<T>::fct_0(), qx::QxClass<T>::fct_1(), etc... This feature is a part of introspection engine of QxOrm library, more details in the chapter : Call function dynamically.

Primary key other than default type "long"

By default, the unique id (primary key) of a C++ class registered in QxOrm context is defined as long type (with auto-increment behaviour in database).

It is possible to define a unique id (primary key) of another type (for example, QString type) with QX_REGISTER_PRIMARY_KEY macro.
This macro specializes qx::trait::get_primary_key<T> template to associate a primary key type to a C++ class.

For example, to define a QString primary key for myClass C++ class (mapped to a database table with a column primary key of type VARCHAR), you have to write : QX_REGISTER_PRIMARY_KEY(myClass, QString)

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

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

#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_


Composite primary key

QxOrm supports 'multi-columns primary key'.
The class id (primary key) must be defined with following type :
  • QPair or std::pair to define 2 columns ;
  • boost::tuple (or std::tuple) to define from 2 columns to 9 columns.
It is necessary to use QX_REGISTER_PRIMARY_KEY() macro to specialize template and to map class id with multi-columns in database.
The list of multi-columns names must be defined with '|' character as separator : 'column1|column2|column3|etc...'.

Here is an example with 'author' class from 'qxBlogCompositeKey' sample project, 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 <QxOrm_Impl.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());
}


public or protected/private data members

To register private or protected data members in QxOrm context (qx::register_class<T> function), it's necessary to declare some friend class.
To do that, QxOrm library provides QX_REGISTER_FRIEND_CLASS(myClass) macro.
An example 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


Namespace

If a class is defined in a namespace, a compilation error occurs with QX_REGISTER_HPP and QX_REGISTER_CPP macros.
To avoid this compilation error, it is necessary to use QX_REGISTER_COMPLEX_CLASS_NAME_HPP and QX_REGISTER_COMPLEX_CLASS_NAME_CPP macros.

You can find a sample in ./test/qxDllSample/dll1/ directory of QxOrm package with CPerson class defined in qx::test namespace :

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

QX_REGISTER_COMPLEX_CLASS_NAME... macros require an extra parameter (in above sample : qx_test_CPerson) to be able to create a global variable. This global variable is created before your application is started. This global variable instance registers the class in QxFactory module (design pattern factory). A C++ class name cannot contain "::" character, so this is why extra parameter replaces all "::" characters by "_".

Built-in C++ supported types

QxOrm library supports all primitive types of C++ standard and Qt framework (numeric, boolean, string, date/time, container, pointer and smart-pointer, etc...). Here is an example with a list (non exhaustive) of supported C++ types mapped to database types (here as SQLite format) :

"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"

Note : it is possible to persist a type not managed by default by QxOrm library. Go to the chapter Persist custom type for more details about this feature.

Other note : to map a C++ type to a database type, please go to the chapter Associate a SQL type to a C++ class for more details.

Register a transient data member

A transient data member is not associated to a column in a database table. QxDao module doesn't use a transient property for all requests to database.

Why to register a transient property in QxOrm context ?
Register a transient data member in QxOrm context enables other features provided by QxOrm library on this property : serialization, introspection, etc...

qx::QxClass<T>::data() class method has an optional parameter named : bool bDao (default value is true). For example, we add a transient property named age to person class (this property doesn't have to be stored in database because we already have a birthDate property) :

namespace qx {
template <> void register_class(QxClass<person> & t)
{
  t.id(& person::id, "id");
  t.data(& person::firstName, "first_name";);
  t.data(& person::lastName, "last_name");
  t.data(& person::birthDate, "birth_date");
  t.data(& person::age, "age", 0, true, false);
}}

Here is another way to define a transient property with qx::IxDataMember instance :

namespace qx {
template <> void register_class(QxClass<person> & t)
{
  t.id(& person::id, "id");
  t.data(& person::firstName, "first_name";);
  t.data(& person::lastName, "last_name");
  t.data(& person::birthDate, "birth_date");

  IxDataMember * pDataMember = t.data(& person::age, "age");
  pDataMember->setDao(false);
}}


Connection to database

To configure a connection to database, you can use the singleton class : qx::QxSqlDatabase.
Here is an example to connect to a SQLite database named test_qxorm.db :

   // Init parameters to connect to database
   qx::QxSqlDatabase::getSingleton()->setDriverName("QSQLITE");
   qx::QxSqlDatabase::getSingleton()->setDatabaseName("./test_qxorm.db");
   qx::QxSqlDatabase::getSingleton()->setHostName("localhost");
   qx::QxSqlDatabase::getSingleton()->setUserName("root");
   qx::QxSqlDatabase::getSingleton()->setPassword("");

By default, all requests sent from QxOrm library to database are executed using qx::QxSqlDatabase singleton class settings. For more details about available settings, it is recommended to read QSqlDatabase Qt class documentation.

Note : qx::QxSqlDatabase singleton class can manage automatically requests to database in several threads (support multi-threading).

Other note : it is possible to manage your own connection pool to database, and also to work with several databases : for more details about this feature, please go to the chapter Working with several databases.

Other note : depending on Qt SQL plugin (setDriverName() method), QxOrm library associates automatically a SQL generator. This SQL generator is used to manage specific features provided by databases. All SQL generators inherit from qx::dao::detail::IxSqlGenerator base class :
   qx::dao::detail::IxSqlGenerator_ptr pSqlGenerator;
   pSqlGenerator.reset(new qx::dao::detail::QxSqlGenerator_MSSQLServer());   
   qx::QxSqlDatabase::getSingleton()->setSqlGenerator(pSqlGenerator);   


Persist a C++ instance in database (insert/update)

All functions to communicate with databases are located in qx::dao namespace.

To save a C++ instance (or a list of C++ instances) to database, QxOrm library provides these functions :
For example :
   // Create 3 drugs instances
   // It is possible to use 'boost' and 'Qt' smart pointer : 'boost::shared_ptr', 'QSharedPointer', etc...
   typedef boost::shared_ptr<drug> drug_ptr;
   drug_ptr d1; d1.reset(new drug()); d1->name = "name1"; d1->description = "desc1";
   drug_ptr d2; d2.reset(new drug()); d2->name = "name2"; d2->description = "desc2";
   drug_ptr d3; d3.reset(new drug()); d3->name = "name3"; d3->description = "desc3";

   // Insert some drugs into a container
   // It is possible to use many containers from 'std', 'boost', 'Qt' and 'qx::QxCollection<Key, Value>'
   typedef std::vector<drug_ptr> type_lst_drug;
   type_lst_drug lst_drug;
   lst_drug.push_back(d1);
   lst_drug.push_back(d2);
   lst_drug.push_back(d3);

   // Insert drugs from container to database
   // 'id' property of 'd1', 'd2' and 'd3' are auto-updated
   QSqlError daoError = qx::dao::insert(lst_drug);

   // Modify and update the second drug into database
   d2->name = "name2 modified";
   d2->description = "desc2 modified";
   daoError = qx::dao::update(d2);


Note : all functions located in qx::dao namespace are flexible because they accept several types of parameters : a simple instance, a list of instances, a pointer, a smart-pointer, a list of pointers, a list of smart-pointers, etc... For example :
  • my_entity t;     /* ... */     qx::dao::insert(t);
  • my_entity * t;     /* ... */     qx::dao::insert(t);
  • std::shared_ptr<my_entity> t;     /* ... */     qx::dao::insert(t);
  • QList<my_entity> lst;     /* ... */     qx::dao::insert(lst);
  • QList<std::shared_ptr<my_entity> > lst;     /* ... */     qx::dao::insert(lst);
About supported collection types by QxOrm library, please go to the chapter : Supported containers.
About supported smart-pointer types by QxOrm library, please go to the chapter : Supported smart pointers.


Delete an instance in database (delete)

All functions to communicate with databases are located in qx::dao namespace.

To delete a C++ instance (or a list of C++ instances) from database, QxOrm library provides these functions :
For example :
   // Create a drug instance with id '18'
   drug d; d.setId(18);

   // Delete the drug with id '18' from database
   QSqlError daoError = qx::dao::delete_by_id(d);

   // Delete all drugs from database
   daoError = qx::dao::delete_all<drug>();


Soft delete behaviour (logical delete)

A soft delete doesn't remove rows from a database table (this is not a physical delete) : a new column is added to table definition to flag a row as deleted or not. This extra 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 it is always possible to enable a row which has been previously deleted : you just have to put a NULL or empty value in this extra column.

To define a soft delete behaviour with QxOrm library, you have to use qx::QxSoftDelete class in mapping function qx::register_class<T>.
Here is an example with Bar class 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 generated by QxOrm library will take into account this soft delete parameter to add conditions (don't fetch deleted items, don't delete physically a row, etc...).
For example, if you execute this code with Bar class :

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());

Then output logs are :

[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 these functions : qx::dao::destroy_by_id() and qx::dao::destroy_all().

Other note : it is recommended to define an index on deleted_at extra column to optimize SQL queries execution (better performance).

Get a C++ instance from database (fetch)

All functions to communicate with databases are located in qx::dao namespace.

To fetch automatically all properties of a C++ instance (or a list of C++ instances) mapped to database table columns (and several tables if relationships are defined), QxOrm library provides these functions :
For example :
   // Fetch drug with id '3' into a new variable
   drug_ptr d; d.reset(new drug());
   d->id = 3;
   QSqlError daoError = qx::dao::fetch_by_id(d);


SQL queries

QxOrm library provides several tools to execute SQL queries to database : Note : QxOrm library is based on QtSql module of Qt framework, so it is always possible to call database using QSqlQuery Qt class if QxOrm features are not adapted to solve an issue.

Using qx::QxSqlQuery class (or qx_query alias)

qx::QxSqlQuery class (or its qx_query typedef) is used to communicate with database (to filter, to sort, etc.) in 2 different ways :
  • writing manually a 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 having to deal with 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 qx::QxSqlDatabase::getSingleton()->setSqlPlaceHolderStyle() method :
  • 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 qx::QxSqlQuery class methods to generate SQL 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 another example using several qx::QxSqlQuery class methods :

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 generate following SQL query for MySQL, PostgreSQL and SQLite databases (for Oracle and Microsoft SQL Server, 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 qx_query typedef) :

// 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 : these functions have 2 other optionals parameters :
  • const QStringList & columns : to select columns to fetch (by default, all columns are fetched) ;
  • const QStringList & relation : to select relationships to fetch (one-to-one, one-to-many, many-to-one and many-to-many defined in void qx::register_class<T>() mapping function per class), by default there is no relation fetched.

Using standard SQL or stored procedure

QxOrm library provides 2 functions to execute a stored procedure or a custom SQL query : The first parameter of these functions, of qx::QxSqlQuery type (or qx_query), contains the stored procedure or the custom SQL query to execute.
For more information about qx::QxSqlQuery class, please read this chapter : Using qx::QxSqlQuery class (or qx_query alias)

qx::dao::execute_query<T>() function is a template function : T type must be registered in 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 passed 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();


Transactions (commit, rollback, session)

A database transaction is a sequence of operations performed as a single logical unit of work :
  • if no error 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, then the data manipulations within the transaction are not persisted to the database.
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 connection 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 connection)
  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 connection to each qx::dao::xxx functions (using session.database() method).
Moreover, you can manage your own database connection (from a connection pool for example) using constructor of qx::QxSession class.

qx::QxSession class provides persistent methods to perform CRUD operations.
Here is the same example using qx::QxSession class methods instead of qx::dao functions :

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

  // Create a session : a valid database connection 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)


Relationships

QxOrm library provides a powerful relationship engine to define easily : Note : a full tutorial about relationships based on qxBlog sample project (source code in QxOrm package) is available.

one-to-many (1-n)

A one-to-many (1-n) relationship is defined with method : qx::QxClass<T>::relationOneToMany(). This method returns an instance of qx::IxSqlRelation type (which is the base class to register a relationship) and take 3 parameters :
  • V U::* pData : reference to the class data member ;
  • const QString & sKey : unique key associated to the relationship ;
  • const QString & sForeignKey : foreign key defined in the linked class/table.

For example : imagine an author (a person) who can write several blog : we will show how to define a one-to-many relationship.
Here are the 2 tables in database :

qxBlog.table.author

author.h file :
#ifndef _QX_BLOG_AUTHOR_H_
#define _QX_BLOG_AUTHOR_H_

class blog;

class QX_BLOG_DLL_EXPORT 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;
// -- constructor, 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_

author.cpp file :
#include "../include/precompiled.h"

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

#include <QxOrm_Impl.h>

QX_REGISTER_CPP_QX_BLOG(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());
}


many-to-one (n-1)

A many-to-one (n-1) relationship is defined with method : qx::QxClass<T>::relationManyToOne(). This method returns an instance of qx::IxSqlRelation type (which is the base class to register a relationship) and take 2 parameters :
  • V U::* pData : reference to the class data member ;
  • const QString & sKey : unique key associated to the relationship (mapped to a table column in database) ;

For example : a comment is associated to a blog and a blog can contain several comment : we will show how to define a many-to-one relationship.
Here are the 2 tables in database :

qxBlog.table.comment

comment.h file :
#ifndef _QX_BLOG_COMMENT_H_
#define _QX_BLOG_COMMENT_H_

class blog;

class QX_BLOG_DLL_EXPORT comment
{
public:
// -- typedef
   typedef boost::shared_ptr<blog> blog_ptr;
// -- properties
   long        m_id;
   QString     m_text;
   QDateTime   m_dt_create;
   blog_ptr    m_blog;
// -- constructor, virtual destructor
   comment() : m_id(0) { ; }
   virtual ~comment() { ; }
};

QX_REGISTER_HPP_QX_BLOG(comment, qx::trait::no_base_class_defined, 0)

typedef boost::shared_ptr<comment> comment_ptr;
typedef QList<comment_ptr> list_comment;

#endif // _QX_BLOG_COMMENT_H_

comment.cpp file :
#include "../include/precompiled.h"

#include "../include/comment.h"
#include "../include/blog.h"

#include <QxOrm_Impl.h>

QX_REGISTER_CPP_QX_BLOG(comment)

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

   t.data(& comment::m_text, "comment_text");
   t.data(& comment::m_dt_create, "date_creation");

   t.relationManyToOne(& comment::m_blog, "blog_id");
}}


many-to-many (n-n)

A many-to-many (n-n) relationship is defined with method : qx::QxClass<T>::relationManyToMany(). This method returns an instance of qx::IxSqlRelation type (which is the base class to register a relationship) and take 5 parameters :
  • V U::* pData : reference to the class data member ;
  • const QString & sKey : unique key associated to the relationship ;
  • const QString & sExtraTable : extra table name to store id of each side of relationship ;
  • const QString & sForeignKeyOwner : foreign key in extra table linked to current class ;
  • const QString & sForeignKeyDataType : foreign key in extra table linked to other class.

For example : a category embed several blog and a blog can belong to several category : we will show how to define a many-to-many relationship. A n-n relationship requires an extra table to store id of each side of relationship.
Here are the 3 tables in database :

qxBlog.table.category

category.h file :
#ifndef _QX_BLOG_CATEGORY_H_
#define _QX_BLOG_CATEGORY_H_

class blog;

class QX_BLOG_DLL_EXPORT category
{
public:
// -- typedef
   typedef boost::shared_ptr<blog> blog_ptr;
   typedef qx::QxCollection<long, blog_ptr> list_blog;
// -- properties
   long        m_id;
   QString     m_name;
   QString     m_desc;
   list_blog   m_blogX;
// -- constructor, virtual destructor
   category() : m_id(0) { ; }
   virtual ~category() { ; }
};

QX_REGISTER_HPP_QX_BLOG(category, qx::trait::no_base_class_defined, 0)

typedef QSharedPointer<category> category_ptr;
typedef qx::QxCollection<long, category_ptr> list_category;

#endif // _QX_BLOG_CATEGORY_H_

category.cpp file :
#include "../include/precompiled.h"

#include "../include/category.h"
#include "../include/blog.h"

#include <QxOrm_Impl.h>

QX_REGISTER_CPP_QX_BLOG(category)

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

   t.data(& category::m_name, "name");
   t.data(& category::m_desc, "description");

   t.relationManyToMany(& category::m_blogX, "list_blog", "category_blog", "category_id", "blog_id");
}}


one-to-one (1-1)

A one-to-one (1-1) relationship links 2 entities which share the same database id. A one-to-one (1-1) relationship is defined with method : qx::QxClass<T>::relationOneToOne(). This method returns an instance of qx::IxSqlRelation type (which is the base class to register a relationship) and take 2 parameters :
  • V U::* pData : reference to the class data member ;
  • const QString & sKey : unique key associated to the relationship ;

For example : imagine a person table and an author table : an author is also a person, so these 2 tables could share the same id in database. Here are the 2 tables in database (with person_id == author_id) :

qxBlog.table.person


Fetch relationships

QxOrm library supports 4 kind of relationships to link C++ classes registered in 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 these items :
  • relation key : each relation is associated to a key defined in qx::register_class<T> mapping function ;
  • "*" keyword means "fetch all relationships defined in 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", following 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 these 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 generates 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 generates 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 generates 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


Select columns fetching relationships and define custom SQL alias

It is sometimes necessary to not request all table columns to optimize : indeed, selecting columns really used by a process limits network traffic between database and C++ application, so performance are improved.

About relationships, QxOrm library provides a syntax to select columns to fetch, using format : my_relation { col_1, col_2, etc... }. By default, if this syntax is not used, then QxOrm library fetches all columns.

For example : imagine a query to fetch :
  • only blog_text column of blog table ;
  • only name and birthdate columns of author table ;
  • only comment_text column of comment table.
   // Fetch relations defining columns to fetch with syntax { col_1, col_2, etc... }
   list_blog lstBlogComplexRelation;
   QStringList relations = QStringList() << "{ blog_text }" << "author_id { name, birthdate }" << "list_comment { comment_text }";
   QSqlError daoError = qx::dao::fetch_all_with_relation(relations, lstBlogComplexRelation);

   qx::dump(lstBlogComplexRelation);
   qAssert(lstBlogComplexRelation.size() > 0);
   qAssert(lstBlogComplexRelation[0]->m_text != ""); // Fetched
   qAssert(lstBlogComplexRelation[0]->m_dt_creation.isNull()); // Not fetched
   qAssert(lstBlogComplexRelation[0]->m_author->m_sex == author::unknown); // Not fetched
   qAssert(lstBlogComplexRelation[0]->m_author->m_name != ""); // Fetched
   qAssert(lstBlogComplexRelation[0]->m_commentX.size() > 0);
   qAssert(lstBlogComplexRelation[0]->m_commentX[0]->m_dt_create.isNull()); // Not fetched
   qAssert(lstBlogComplexRelation[0]->m_commentX[0]->m_text != ""); // Fetched

Note : another syntax is available to select columns to not fetch : my_relation -{ col_1, col_2, etc... }.

Other note : you can also define a custom SQL alias per relation. This is useful to write your WHERE conditions in the SQL query. A SQL alias can be defined between characters < >.

Example : here is a fetch with relationships example defining some SQL aliases per relation :

list_blog lstBlogComplexRelation3;
QStringList relations;
relations << "<blog_alias> { blog_text }";
relations << "author_id <author_alias> { name, birthdate }";
relations << "list_comment <list_comment_alias> { comment_text } -> blog_id <blog_alias_2> -> * <..._my_alias_suffix>";
QSqlError daoError = qx::dao::fetch_all_with_relation(relations, lstBlogComplexRelation3);
qx::dump(lstBlogComplexRelation3);


This code generates following SQL query :
SELECT blog_alias.blog_id AS blog_alias_blog_id_0, blog_alias.blog_text AS blog_alias_blog_text_0, blog_alias.author_id AS blog_alias_author_id_0, author_alias.author_id AS author_alias_author_id_0, author_alias.name AS author_alias_name_0, author_alias.birthdate AS author_alias_birthdate_0, list_comment_alias.comment_id AS list_comment_alias_comment_id_0, list_comment_alias.blog_id AS list_comment_alias_blog_id_0, list_comment_alias.comment_text AS list_comment_alias_comment_text_0, list_comment_alias.blog_id AS list_comment_alias_blog_id_0_2, blog_alias_2.blog_id AS blog_alias_2_blog_id_0, blog_alias_2.blog_text AS blog_alias_2_blog_text_0, blog_alias_2.date_creation AS blog_alias_2_date_creation_0, blog_alias_2.author_id AS blog_alias_2_author_id_0_3, author_my_alias_suffix.author_id AS author_my_alias_suffix_author_id_0, author_my_alias_suffix.name AS author_my_alias_suffix_name_0, author_my_alias_suffix.birthdate AS author_my_alias_suffix_birthdate_0, author_my_alias_suffix.sex AS author_my_alias_suffix_sex_0, comment_my_alias_suffix.comment_id AS comment_my_alias_suffix_comment_id_0, comment_my_alias_suffix.blog_id AS comment_my_alias_suffix_blog_id_0, comment_my_alias_suffix.comment_text AS comment_my_alias_suffix_comment_text_0, comment_my_alias_suffix.date_creation AS comment_my_alias_suffix_date_creation_0, comment_my_alias_suffix.blog_id AS comment_my_alias_suffix_blog_id_0_5, category_my_alias_suffix.category_id AS category_my_alias_suffix_category_id_0, category_my_alias_suffix.name AS category_my_alias_suffix_name_0, category_my_alias_suffix.description AS category_my_alias_suffix_description_0
  FROM blog AS blog_alias
  LEFT OUTER JOIN author author_alias ON author_alias.author_id = blog_alias.author_id
  LEFT OUTER JOIN comment list_comment_alias ON list_comment_alias.blog_id = blog_alias.blog_id
  LEFT OUTER JOIN blog blog_alias_2 ON blog_alias_2.blog_id = list_comment_alias.blog_id
  LEFT OUTER JOIN author author_my_alias_suffix ON author_my_alias_suffix.author_id = blog_alias_2.author_id
  LEFT OUTER JOIN comment comment_my_alias_suffix ON comment_my_alias_suffix.blog_id = blog_alias_2.blog_id
  LEFT OUTER JOIN category_blog category_blog_6 ON blog_alias_2.blog_id = category_blog_6.blog_id
  LEFT OUTER JOIN category category_my_alias_suffix ON category_blog_6.category_id = category_my_alias_suffix.category_id


Add SQL query inside LEFT OUTER JOIN / INNER JOIN

qx::QxSqlQuery class (or its qx_query alias) provides the following method :

QxSqlQuery & QxSqlQuery::addJoinQuery(const QString & relationKeyOrAlias, const QxSqlQuery & joinQuery);


The qx::QxSqlQuery::addJoinQuery() method inserts SQL sub-queries inside LEFT OUTER JOIN / INNER JOIN.
For example :

// Test to add join SQL sub-queries (inside LEFT OUTER JOIN or INNER JOIN)
list_blog lstBlogWithJoinQueries;
qx_query query = qx_query().where("blog_alias.blog_text").isEqualTo("update blog_text_1");
query.addJoinQuery("list_comment_alias", "AND list_comment_alias.comment_text IS NOT NULL");
query.addJoinQuery("author_alias", qx_query().freeText("AND author_alias.sex = :sex", QVariantList() << author::female));
daoError = qx::dao::fetch_by_query_with_relation(QStringList() << "<blog_alias> { blog_text }" << "author_id <author_alias> { name, birthdate, sex }" 
                                                               << "list_comment <list_comment_alias> { comment_text }", query, lstBlogWithJoinQueries);
qx::dump(lstBlogWithJoinQueries);
qAssert(lstBlogWithJoinQueries.size() > 0);
qAssert(lstBlogWithJoinQueries[0]->m_text == "update blog_text_1");
qAssert(lstBlogWithJoinQueries[0]->m_author->m_sex == author::female);


Above C++ code will build following SQL query :

SELECT blog_alias.blog_id AS blog_alias_blog_id_0, blog_alias.blog_text AS blog_alias_blog_text_0, blog_alias.author_id AS blog_alias_author_id_0, author_alias.author_id AS author_alias_author_id_0, author_alias.name AS author_alias_name_0, author_alias.birthdate AS author_alias_birthdate_0, author_alias.sex AS author_alias_sex_0, list_comment_alias.comment_id AS list_comment_alias_comment_id_0, list_comment_alias.blog_id AS list_comment_alias_blog_id_0, list_comment_alias.comment_text AS list_comment_alias_comment_text_0
  FROM blog AS blog_alias
  LEFT OUTER JOIN author author_alias ON (author_alias.author_id = blog_alias.author_id
      AND author_alias.sex = :sex)
  LEFT OUTER JOIN comment list_comment_alias ON (list_comment_alias.blog_id = blog_alias.blog_id
      AND list_comment_alias.comment_text IS NOT NULL)
  WHERE blog_alias.blog_text = :blog_alias_blog_text_1_0


Supported containers

QxOrm library supports several containers provided by Qt, boost and std standard library. QxOrm library provides also its own container, named qx::QxCollection, especially designed to store data fetched from database. So the developer is not restricted : QxOrm library offers a large choice of containers.

Qt containers

  QList<T>  
  QVector<T>  
  QSet<T>  
  QLinkedList<T>  
  QHash<Key, Value>  
  QMap<Key, Value>  
  QMultiHash<Key, Value>  
  QMultiMap<Key, Value>  

Boost containers

  boost::unordered_map<Key, Value>  
  boost::unordered_set<T>  
  boost::unordered_multimap<Key, Value>  
  boost::unordered_multiset<T>  

std containers

  std::list<T>  
  std::vector<T>  
  std::set<T>  
  std::map<Key, Value>  

  std::unordered_map<Key, Value>  
  std::unordered_set<T>  
  std::unordered_multimap<Key, Value>  
  std::unordered_multiset<T>  

qx::QxCollection

There are several containers provided by stl, boost and Qt libraries.
So, it is legitimate to ask this question : what is qx::QxCollection<Key, Value> for ?
qx::QxCollection<Key, Value> is a new container (based on the excellent boost::multi_index_container library) which has following features :
  • keeps insertion order of items in the list ;
  • quick access to an item by its index : is equivalent to std::vector<T> or QList<T> for example ;
  • quick access to an item 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 ;
  • thread-safe.
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 matches to an object of std::pair<Key, Value> type.
To get a more natural and more readable result, it is advised to use the _foreach macro : this macro uses BOOST_FOREACH for all containers except for qx::QxCollection<Key, Value>.
In this case, the returned element matches to the Value type (cf. sample).
_foreach macro is compatible with all containers (stl, Qt, boost...) since it uses BOOST_FOREACH macro.

Additional note : qx::QxCollection<Key, Value> is especially designed to receive data resulting from a database.
Indeed, these data can be sorted (using ORDER BY in a SQL query for example), it is thus important to keep insertion order of items 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 item 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();


Supported smart pointers

QxOrm library supports several smart pointers provided by Qt, boost and std standard library. QxOrm library provides also its own smart pointer, named qx::dao::ptr, which provides new features when used with qx::dao functions. So the developer is not restricted : QxOrm library offers a large choice of smart pointers.

Qt smart pointers

  QSharedPointer<T>  
  QScopedPointer<T>  
  QWeakPointer<T>  
  QSharedDataPointer<T>  

Boost smart pointers

  boost::shared_ptr<T>  
  boost::intrusive_ptr<T>  
  boost::scoped_ptr<T>  
  boost::weak_ptr<T>  

std smart pointers

  std::shared_ptr<T>  
  std::unique_ptr<T>  
  std::weak_ptr<T>  

qx::dao::ptr

QxOrm library can be used with smart-pointers provided by std, 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);


Triggers

With QxOrm Triggers, it is possible to execute custom process before and/or after an insert, update or delete query in database.
You can find a sample in ./test/qxDllSample/dll2/ directory of QxOrm package with BaseClassTrigger class.
BaseClassTrigger sample class contains 5 properties : m_id, m_dateCreation, m_dateModification, m_userCreation and m_userModification.
In the following example, 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 'qx::dao::detail::QxDao_Trigger<T>' 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 <QxOrm_Impl.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";
}


Validators

QxValidator module of QxOrm library provides a validation engine for classes registered in QxOrm context.
To use this validation engine, you have to define your constraints into the mapping function per class : void qx::register_class<T>.
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 process can be executed in several layers of your application without having to duplicate any of these rules (presentation layer, data access layer).

Here is a description of some classes defined in QxValidator module :
  • qx::IxValidator : each constraint defined in void qx::register_class<T> function is associated with an interface of qx::IxValidator type ;
  • qx::IxValidatorX : the list of constraints is associated with an interface of qx::IxValidatorX type. You can iterate over this collection during program execution : it could be interesting for example to generate DDL SQL schema and to take into account some validation rules into database (read the chapter : Generate database DDL SQL schema) ;
  • qx::QxInvalidValueX : when an instance is invalid, list of constraints violation are inserted into a qx::QxInvalidValueX collection ;
  • qx::QxInvalidValue : each item into this collection is a qx::QxInvalidValue type and contains an error message (description to explain why the instance is not valid).
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 <QxOrm_Impl.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 above source 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 these default messages (for example, a translation) 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.

Manage NULL database value

All databases manages NULL value : for more details about NULL value, please read the Wikipedia web page.
QxOrm library provides several ways to manage NULL value :
  • using boost::optional class provided by boost library ;
  • using QVariant class provided by Qt framework ;
  • using pointers or smart-pointers : a C++ NULL pointer is associated to a NULL value in database.

boost::optional

boost::optional<T> class provided by boost is the best solution to manage NULL value in C++.
To use boost::optional<T> with QxOrm library, you must define _QX_ENABLE_BOOST compilation option, or include <QxExtras/QxBoostOptionalOnly.h> header file.
Here is an example where all properties (except primary key) can be NULL using boost::optional class :

#ifndef _PERSON_H_
#define _PERSON_H_

class person
{
public:
   long id;
   boost::optional<QString> firstName;
   boost::optional<QString> lastName;
   boost::optional<QDateTime> birthDate;  

   person() : id(0) { ; }
   virtual ~person() { ; }
};

#endif // _PERSON_H_

boost::optional<T> class is very easy to use : please read documentation on boost website for more details.

QVariant

QVariant class provided by Qt is another way to manage NULL value in C++.
Here is a class example where all values (except primary key) can be NULL using QVariant class :

#ifndef _PERSON_H_
#define _PERSON_H_

class person
{
public:
   long id;
   QVariant firstName;
   QVariant lastName;
   QVariant birthDate;  

   person() : id(0) { ; }
   virtual ~person() { ; }  
};

#endif // _PERSON_H_

This solution is not perfect compared to boost::optional<T> because you loose the property type.
So it is recommended to work with boost::optional<T> class to manage NULL value with QxOrm library.

Inheritance and polymorphism

With ORM tools, there are 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 for more details about ORM inheritance and database.
You can find a sample in ./test/qxDllSample/dll2/ directory of QxOrm package with BaseClassTrigger class.

qx::IxPersistable interface (abstract class)

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 in 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 these methods, goto QxOrm library online class 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::IxPersistableCollection_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 to several database tables, 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 <QxOrm_Impl.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 engine of 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 :

Use PIMPL C++ pattern (Private Implementation idiom or d-pointer)

From cppreference website : "Pointer to implementation" or "pImpl" is a C++ programming technique that removes implementation details of a class from its object representation by placing them in a separate class, accessed through an opaque pointer. This technique is used to construct C++ library interfaces with stable ABI and to reduce compile-time dependencies.

Advantages of PIMPL pattern for C++ persistent classes registered into QxOrm context :
  • Compilation Firewall : if the private implementation changes, the client code doesn't have to be recompiled ;
  • Reduce compilation times : headers files (*.h, *.hpp) are smaller ;
  • Binary Compatibility : as long as the binary interface stays the same, you can link your app to a different version of a library ;
  • Reduce the size of generated binaries (*.dll, *.so, *.exe, etc...).
Disadvantages of PIMPL :
  • Performance : one level of indirection is added ;
  • Memory Management : a memory chunk has to be allocated (or preallocated) for the private implementation (can issue memory fragmentation).

QxOrm library provides a sample project where all persistent classes are developed using the PIMPL pattern : qxBlogPImpl (with relationships).
It is also possible (and recommended) to use QxEntityEditor application to export automatically all C++ persistent classes of a project with the PIMPL option.

Here is an example of a C++ class registered into QxOrm context developed with the PIMPL idiom (with 1-n, n-1 and n-n relationships) :

#ifndef _QX_BLOG_BLOG_H_
#define _QX_BLOG_BLOG_H_

class author;
class comment;
class category;

class QX_BLOG_DLL_EXPORT blog
{

   QX_REGISTER_FRIEND_CLASS(blog)

private:

   struct blog_impl;
   std::unique_ptr<blog_impl> m_pImpl; //!< Private implementation idiom

public:

   blog();
   virtual ~blog();

   blog(const blog & other);
   blog & operator=(const blog & other);

#ifdef Q_COMPILER_RVALUE_REFS
   blog(blog && other) Q_DECL_NOEXCEPT;
   blog & operator=(blog && other) Q_DECL_NOEXCEPT;
#endif // Q_COMPILER_RVALUE_REFS

   long id() const;
   QString text() const;
   QDateTime dateCreation() const;

   void setId(long l);
   void setText(const QString & s);
   void setDateCreation(const QDateTime & d);

   std::shared_ptr<author> & getAuthor();
   QList< std::shared_ptr<comment> > & listOfComments();
   qx::QxCollection<long, QSharedPointer<category> > & listOfCategories();

};

QX_REGISTER_HPP_QX_BLOG(blog, qx::trait::no_base_class_defined, 0)

typedef std::shared_ptr<blog> blog_ptr;
typedef std::vector<blog_ptr> list_blog;

#endif // _QX_BLOG_BLOG_H_

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

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

#include <QxOrm_Impl.h>

QX_REGISTER_CPP_QX_BLOG(blog)

struct Q_DECL_HIDDEN blog::blog_impl
{
   long           m_id;
   QString        m_text;
   QDateTime      m_dt_creation;
   author_ptr     m_author;
   list_comment   m_commentX;
   list_category  m_categoryX;

   blog_impl() : m_id(0) { ; }
   ~blog_impl() { ; }
};

namespace qx {
template <> void register_class(QxClass<blog> & t)
{
   IxDataMember * pImpl = t.pimpl(& blog::m_pImpl);

   t.id(& blog::blog_impl::m_id, "blog_id", 0, pImpl);

   t.data(& blog::blog_impl::m_text, "blog_text", 0, true, true, pImpl);
   t.data(& blog::blog_impl::m_dt_creation, "date_creation", 0, true, true, pImpl);

   t.relationManyToOne(& blog::blog_impl::m_author, "author_id", 0, pImpl);
   t.relationOneToMany(& blog::blog_impl::m_commentX, "list_comment", "blog_id", 0, pImpl);
   t.relationManyToMany(& blog::blog_impl::m_categoryX, "list_category", "category_blog", "blog_id", "category_id", 0, pImpl);
}}

blog::blog() : m_pImpl(new blog_impl()) { ; }

blog::~blog() { ; }

blog::blog(const blog & other) : m_pImpl(new blog_impl(* other.m_pImpl)) { ; }

blog & blog::operator=(const blog & other)
{
   if (this != (& other)) { (* m_pImpl) = (* other.m_pImpl); }
   return (* this);
}

#ifdef Q_COMPILER_RVALUE_REFS
blog::blog(blog && other) Q_DECL_NOEXCEPT : m_pImpl(std::move(other.m_pImpl)) { ; }
blog & blog::operator=(blog && other) Q_DECL_NOEXCEPT { if (this != (& other)) { m_pImpl = std::move(other.m_pImpl); }; return (* this); }
#endif // Q_COMPILER_RVALUE_REFS

long blog::id() const { return m_pImpl->m_id; }

QString blog::text() const { return m_pImpl->m_text; }

QDateTime blog::dateCreation() const { return m_pImpl->m_dt_creation; }

void blog::setId(long l) { m_pImpl->m_id = l; }

void blog::setText(const QString & s) { m_pImpl->m_text = s; }

void blog::setDateCreation(const QDateTime & d) { m_pImpl->m_dt_creation = d; }

std::shared_ptr<author> & blog::getAuthor() { return m_pImpl->m_author; }

QList< std::shared_ptr<comment> > & blog::listOfComments() { return m_pImpl->m_commentX; }

qx::QxCollection<long, QSharedPointer<category> > & blog::listOfCategories() { return m_pImpl->m_categoryX; }


Persist custom type

QxOrm library can persist every types, not only classes registered in 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 in 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 being 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 QxConvert_ToVariant< ExtObject3D > {
static inline QVariant toVariant(const ExtObject3D & t, const QString & format, int index, qx::cvt::context::ctx_type ctx)
{ /* Here I convert from ExtObject3D to QVariant */ } };

template <> struct QxConvert_FromVariant< ExtObject3D > {
static inline qx_bool fromVariant(const QVariant & v, ExtObject3D & t, const QString & format, int index, qx::cvt::context::ctx_type ctx)
{ /* Here I convert from QVariant to ExtObject3D */; return qx_bool(true); } };

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


Note : here is a template to create a custom persistable type :

#ifndef _MY_CUSTOM_PERSISTABLE_TYPE_H_
#define _MY_CUSTOM_PERSISTABLE_TYPE_H_

#ifdef _MSC_VER
#pragma once
#endif

#include <QxOrm.h>

class MyPersistableType
{
   /* What you want here */
};

QX_REGISTER_CLASS_NAME(MyPersistableType)
QX_CLASS_VERSION(MyPersistableType, 0)

QDataStream & operator<< (QDataStream & stream, const MyPersistableType & t)
{
   /* Your implementation here */
}

QDataStream & operator>> (QDataStream & stream, MyPersistableType & t)
{
   /* Your implementation here */
}

namespace qx {
namespace cvt {
namespace detail {

template <> struct QxConvert_ToVariant< MyPersistableType > {
static inline QVariant toVariant(const MyPersistableType & t, const QString & format, int index, qx::cvt::context::ctx_type ctx)
{
   /* Here I convert from MyPersistableType to QVariant */
} };

template <> struct QxConvert_FromVariant< MyPersistableType > {
static inline qx_bool fromVariant(const QVariant & v, MyPersistableType & t, const QString & format, int index, qx::cvt::context::ctx_type ctx)
{
   /* Here I convert from QVariant to MyPersistableType */
   return qx_bool(true);
} };

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

#ifndef _QX_NO_JSON

namespace qx {
namespace cvt {
namespace detail {

template <>
struct QxConvert_ToJson< MyPersistableType >
{
   static inline QJsonValue toJson(const MyPersistableType & t, const QString & format)
   {
      /* Your implementation here */
   }
};

template <>
struct QxConvert_FromJson< MyPersistableType >
{
   static inline qx_bool fromJson(const QJsonValue & j, MyPersistableType & t, const QString & format)
   {
      /* Your implementation here */
   }
};

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

#endif // _QX_NO_JSON

// ------------------------------------
// If you are using boost serialization, you have also to implement save/load functions like above 'ExtObject3D' example
// ------------------------------------

#endif // _MY_CUSTOM_PERSISTABLE_TYPE_H_


Generate database DDL SQL schema

!!! It's strongly recommended to use QxEntityEditor application to manage DDL 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 in 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 <QxOrm_Impl.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 in 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 in QxOrm context : instead of using "db.tables()" Qt function, it could be possible to fetch all tables with more information (version number for each table, columns count registered in QxOrm context, table description, etc.).

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 (read chapter : Persist custom type), 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")


Async database queries

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::IxPersistableCollection_ptr lst = pDaoParams->pListOfInstances;
   //...
}


Cache to store C++ instances (QxCache module)

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();


Working with several databases

In the Connection to database chapter, we have seen how to configure default connection to database using singleton class : qx::QxSqlDatabase. QxOrm library is based on Qt QtSql engine, so QxOrm uses internally the Qt QSqlDatabase class. All functions to communicate with databases (qx::dao namespace, qx::QxSession class, etc...) have an optional parameter named : QSqlDatabase * pDatabase = NULL :
  • if value of this parameter is NULL (which is the default value) : then QxOrm library uses qx::QxSqlDatabase settings to connect to database (support multi-threading) ;
  • if value is not NULL : then QxOrm library uses connection provided by this QSqlDatabase * pDatabase pointer.
So this optional parameter can be used to manage your own connection pool and/or to connect to several databases.

Register an abstract class in QxOrm context

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

Register automatically Qt meta-properties (Q_PROPERTY macro)

All classes inherited from QObject type can use Q_PROPERTY macro : these 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 requires to register all properties per class in the void qx::register_class<T>() mapping function to provide all features (persistence, XML, JSON and binary serialization, etc.). It's possible to register automatically all Qt meta-properties in QxOrm context without having 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 in ./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 <QxOrm_Impl.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 in a base class.

All properties defined with Q_PROPERTY macro can be registered in 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 in 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 in 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 relationship 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).

Serialization

From Wikipedia web page : serialization is the process of translating data structures or object state into a format that can be stored (for example, in a file or memory buffer, or transmitted across a network connection link) and reconstructed later in the same or another computer environment. When the resulting series of bits is reread according to the serialization format, it can be used to create a semantically identical clone of the original object. The opposite operation, extracting a data structure from a series of bytes, is named deserialization.

Each C++ class registered in QxOrm context can be serialized in several ways : Note : serialization engine of QxOrm library provides extra features like : clone entity, dump entity (XML or JSON format) and QxService module.

Other note : by default, all properties registered in QxOrm context are serializable. To remove a property from the serialization engine, you can write :

namespace qx {
template <> void register_class(QxClass<person> & t)
{
  IxDataMember * pDataMember = t.data(& person::age, "age");
  pDataMember->setSerialize(false);
}}


Version number to manage ascendant compatibility

Ascendant compatibility allows deserialization process (so restore a data structure) from a stream generated by a previous version of an application. QxOrm library requires a version number per class and a version number for each property registered in QxOrm context to provide ascendant compatibility.

For example, imagine a person class created in a version A of your application : we put in QX_REGISTER_HPP macro a class version equals to 0 (means first version of our person class), and each property class have also a version equals to 0 (0 is the default value, optional parameter). So our person class looks like :

* person.h file :
#ifndef _PERSON_H_
#define _PERSON_H_

class person
{
public:
   long id;
   QString firstName;
   QString lastName;
   QDateTime birthDate;

   person() : id(0) { ; }
   virtual ~person() { ; }
};

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

#endif // _PERSON_H_

* person.cpp file :
namespace qx {
template <> void register_class(QxClass<person> & t)
{
  t.id(& person::id, "id");
  t.data(& person::firstName, "first_name", 0);
  t.data(& person::lastName, "last_name", 0);
  t.data(& person::birthDate, "birth_date", 0);
}}


In version B of the application, we modify our person class and we add 2 properties : sex and address. Our class has changed, so we have to increment its class version number, and new properties must have a version equals to 1. Now, our person class looks like :

* person.h file :
#ifndef _PERSON_H_
#define _PERSON_H_

class person
{
public:
   long id;
   QString firstName;
   QString lastName;
   QDateTime birthDate;
   QString sex;
   QString address;

   person() : id(0) { ; }
   virtual ~person() { ; }
};

QX_REGISTER_HPP_MY_TEST_EXE(person, qx::trait::no_base_class_defined, 1)

#endif // _PERSON_H_

* person.cpp file :
namespace qx {
template <> void register_class(QxClass<person> & t)
{
  t.id(& person::id, "id");
  t.data(& person::firstName, "first_name", 0);
  t.data(& person::lastName, "last_name", 0);
  t.data(& person::birthDate, "birth_date", 0);
  t.data(& person::sex, "sex", 1);
  t.data(& person::address, "address", 1);
}}


Note : QxOrm library can serialize this person class from application in version A, then deserialize this version A stream to create a cloned instance of person class in version B of our application.

Other note : remove a property breaks ascendant compatibility. So it is recommended to never remove a property to work with QxOrm serialization engine : it is possible for example to put a private visibility and to delete get/set accessors, so property is hidden and can be considered as obsolete.

Qt QDataStream engine

Each C++ class registered in QxOrm context can be serialized using Qt QDataStream engine. Functions to use Qt QDataStream serialization are available in namespace : qx::serialization::qt. Note : QDataStream serialization is portable (support serialization/deserialization on all environments : Windows, Linux, Mac OS X, etc...). The output serialized stream is in binary format : so stream size is smaller than XML or JSON for example. QDataStream serialization is based on introspection engine of QxOrm library, so it is slower than boost::serialization engine (based on C++ template).

For example :
   // Fetch a drug with id '3' in a new variable
   // drug is a C++ class registered in QxOrm context
   drug d;
   d.id = 3;
   QSqlError daoError = qx::dao::fetch_by_id(d);

   // Serialize the drug to a file
   qx::serialization::qt::to_file(d, "export_drug.txt");

   // Import drug from file in a new instance
   drug d2;
   qx::serialization::qt::from_file(d2, "export_drug.txt");

   // Check if d == d2
   qAssert(d == d2);

Note : in above example, we serialize a C++ instance. All functions in qx::serialization namespace can serialize list of objects. For more details about supported containers, please read this chapter : Supported containers.

Qt JSON engine

Each C++ class registered in QxOrm context can be serialized to JSON using Qt QJson engine (requires Qt5). Functions to use Qt JSON serialization are available in namespace : qx::serialization::json. Note : JSON serialization engine is the most permissive (compared to XML engine for example) : indeed, properties can be defined in any order, and properties can be removed or added. JSON deserialization doesn't generate errors or throw exceptions : the engine ignores invalid or removed properties (but JSON stream must be valid) : so JSON engine is much more flexible than XML engine.

Other note : JSON serialization is based on introspection engine of QxOrm library, so it is slower than boost::serialization engine (based on C++ template).

For example :
   // Fetch a list of authors from database and serialize them to a JSON file
   list_author list_of_author;
   qx::dao::fetch_all(list_of_author);
   qx::serialization::json::to_file(list_of_author, "list_of_author.json");

Above example generates following JSON stream :
{
    "author_id_2": {
        "author_id": "author_id_2",
        "birthdate": "2016-03-24",
        "list_blog": [
        ],
        "name": "author_2",
        "sex": 1
    },
    "author_id_3": {
        "author_id": "author_id_3",
        "birthdate": "2016-03-24",
        "list_blog": [
        ],
        "name": "author_3",
        "sex": 1
    }
}


Note : QxRestApi module provided by QxOrm library is based on JSON serialization engine.

Other note : you can customize output JSON format (to filter some properties generated by JSON serialization process). All JSON serialization functions provide an optional parameter of type QString named format. Prerequisites to use this format parameter are :
  • format parameter must start with prefix : filter: ;
  • output properties can be defined inside { } ;
  • relationships are splitted by character | ;
  • character * means : all relationships for 1 level ;
  • character - before { } means : all properties except.

Example : here is a JSON serialization example defining an output format to filter some properties on several levels of relationships :

// Serialize a C++ instance to a JSON string
QString jsonFormat = "filter: { blog_text } | author_id { name, birthdate } | list_comment { comment_text } -> blog_id -> *";
QString outputJsonFiltered = qx::serialization::json::to_string(blog, 1, jsonFormat);
qDebug("[QxOrm] custom JSON serialization process (filtered) : \n%s", qPrintable(outputJsonFiltered));

// Fill a C++ instance based on a JSON string
blog_ptr blogFromJsonFiltered; blogFromJsonFiltered.reset(new blog());
qx::serialization::json::from_string(blogFromJsonFiltered, outputJsonFiltered, 1, jsonFormat);
qx::dump(blogFromJsonFiltered);
qAssert(blogFromJsonFiltered->m_text != ""); // Fetched
qAssert(blogFromJsonFiltered->m_dt_creation.isNull()); // Not fetched
qAssert(blogFromJsonFiltered->m_author->m_sex == author::unknown); // Not fetched
qAssert(blogFromJsonFiltered->m_author->m_name != ""); // Fetched
qAssert(blogFromJsonFiltered->m_commentX.size() > 0);
qAssert(blogFromJsonFiltered->m_commentX[0]->m_dt_create.isNull()); // Not fetched
qAssert(blogFromJsonFiltered->m_commentX[0]->m_text != ""); // Fetched
qAssert(blogFromJsonFiltered->m_commentX[0]->m_blog);


XML boost serialization

XML boost::serialization engine is disabled by default : to enable this feature, it is necessary to define _QX_ENABLE_BOOST_SERIALIZATION and _QX_ENABLE_BOOST_SERIALIZATION_XML compilation options in QxOrm.pri (or QxOrm.cmake) configuration file. It is also required to build boost::serialization binary (because this module is not header only), and to set the path to this boost serialization binary to QX_BOOST_LIB_PATH, QX_BOOST_LIB_SERIALIZATION_DEBUG and QX_BOOST_LIB_SERIALIZATION_RELEASE variables of QxOrm.pri (or QxOrm.cmake) configuration file.

Each C++ class registered in QxOrm context can be serialized using XML boost::serialization engine. Functions to work with XML boost serialization are available in namespace : qx::serialization::xml (same functions as qx::serialization::qt namespace).

XML boost serialization engine is :
  • portable : support serialization/deserialization on all environments : Windows, Linux, Mac OS X, etc... ;
  • slowest : slower than binary and text serialization ;
  • largest : generated stream are bigger than binary and text serialization ;
  • human-readable : a XML stream can easily be parsed by a text editor and can be read by a human.

Binary boost serialization

Binary boost::serialization engine is disabled by default : to enable this feature, it is necessary to define _QX_ENABLE_BOOST_SERIALIZATION and _QX_ENABLE_BOOST_SERIALIZATION_BINARY compilation options in QxOrm.pri (or QxOrm.cmake) configuration file. It is also required to build boost::serialization binary (because this module is not header only), and to set the path to this boost serialization binary to QX_BOOST_LIB_PATH, QX_BOOST_LIB_SERIALIZATION_DEBUG and QX_BOOST_LIB_SERIALIZATION_RELEASE variables of QxOrm.pri (or QxOrm.cmake) configuration file.

Each C++ class registered in QxOrm context can be serialized using binary boost::serialization engine. Functions to work with binary boost serialization are available in namespace : qx::serialization::binary (same functions as qx::serialization::qt namespace).

Binary boost serialization engine is :
  • non-portable : an instance serialized on Windows cannot be deserialized on Linux for example : so you have to stay on the same environment ;
  • fastest : faster than XML and text serialization ;
  • smallest : generated stream are smaller than XML and text serialization ;
  • non-human-readable : a binary stream cannot be read (not useful to log for example).

Other boost serialization

boost::serialization engine provides several formats to serialize C++ classes. All boost serialization process are disabled by default, so to use them (same functions as qx::serialization::qt namespace), it is necessary to define compilation options in QxOrm.pri (or QxOrm.cmake) configuration file :

Clone a C++ instance

Each C++ class registered in QxOrm context can be cloned using : For example :

   drug_ptr d1;
   d1.reset(new drug());
   d1->name = "name1";
   d1->description = "desc1";

   // Clone a drug
   drug_ptr d_clone = qx::clone(* d1);

   // Check if (d1 == d_clone)
   qAssert((* d1) == (* d_clone));

Important note : be careful when you clone a smart-pointer (boost::shared_ptr or QSharedPointer for example) where the root item can be referenced several times in its hierarchy (tree structure for example). In this case, to protect the root pointer of a double deletion (2 smart-pointers which take ownership of the same raw pointer), it is recommended to clone this way :

// 'pOther' type is boost::shared_ptr<myClass> (smart-pointer)
boost::shared_ptr<myClass> * pCloneTemp = qx::clone_to_nude_ptr(pOther);
boost::shared_ptr<myClass> pClone = (pCloneTemp ? (* pCloneTemp) : boost::shared_ptr<myClass>());
if (pCloneTemp) { delete pCloneTemp; pCloneTemp = NULL; }
// Now use 'pClone' ...


Dump a C++ instance (XML or JSON format)

Each C++ class registered in QxOrm context can be displayed to JSON format. If XML boost::serialization engine is enabled, then it is also possible to display a XML dump of a C++ instance (second input parameter of qx::dump function). QxOrm dump feature can be useful to debug or to log for example.

   blog_ptr b;
   b.reset(new blog());
   b->id = 36;
   qx::dao::fetch_by_id_with_all_relation(b);

   // Dump 'b' instance result from database (XML or JSON serialization)
   // Second parameter is optional : 'true' = JSON format, 'false' = XML format
   qx::dump(b, false);

Above source code generates output XML :

[QxOrm] start dump 'boost::shared_ptr<blog>'
<boost.shared_ptr-blog- class_id="0" tracking_level="0" version="1">
	<px class_id="1" tracking_level="1" version="0" object_id="_0">
		<blog_id>113</blog_id>
		<blog_text class_id="2" tracking_level="0" version="0">update blog_text_1</blog_text>
		<date_creation class_id="3" tracking_level="0" version="0">20100409162612000</date_creation>
		<author_id class_id="4" tracking_level="0" version="1">
			<px class_id="5" tracking_level="1" version="0" object_id="_1">
				<author_id>author_id_2</author_id>
				<name>author_2</name>
				<birthdate class_id="6" tracking_level="0" version="0">20100409</birthdate>
				<sex>1</sex>
				<list_blog class_id="7" tracking_level="0" version="0">
					<count>0</count>
					<item_version>1</item_version>
				</list_blog>
			</px>
		</author_id>
		<list_comment class_id="8" tracking_level="0" version="0">
			<count>2</count>
			<item class_id="9" tracking_level="0" version="1">
				<px class_id="10" tracking_level="1" version="0" object_id="_2">
					<comment_id>209</comment_id>
					<comment_text>comment_1 text</comment_text>
					<date_creation>20100409162612000</date_creation>
					<blog_id>
						<px class_id_reference="1" object_id="_3">
							<blog_id>113</blog_id>
							<blog_text></blog_text>
							<date_creation></date_creation>
							<author_id>
								<px class_id="-1"></px>
							</author_id>
							<list_comment>
								<count>0</count>
							</list_comment>
							<list_category class_id="11" tracking_level="0" version="0">
								<count>0</count>
							</list_category>
						</px>
					</blog_id>
				</px>
			</item>
			<item>
				<px class_id_reference="10" object_id="_4">
					<comment_id>210</comment_id>
					<comment_text>comment_2 text</comment_text>
					<date_creation>20100409162612000</date_creation>
					<blog_id>
						<px class_id_reference="1" object_id="_5">
							<blog_id>113</blog_id>
							<blog_text></blog_text>
							<date_creation></date_creation>
							<author_id>
								<px class_id="-1"></px>
							</author_id>
							<list_comment>
								<count>0</count>
							</list_comment>
							<list_category>
								<count>0</count>
							</list_category>
						</px>
					</blog_id>
				</px>
			</item>
		</list_comment>
		<list_category>
			<count>2</count>
			<item class_id="12" tracking_level="0" version="0">
				<first>355</first>
				<second class_id="13" tracking_level="0" version="0">
					<qt_shared_ptr class_id="14" tracking_level="1" version="0" object_id="_6">
						<category_id>355</category_id>
						<name>category_1</name>
						<description>desc_1</description>
						<list_blog class_id="15" tracking_level="0" version="0">
							<count>0</count>
						</list_blog>
					</qt_shared_ptr>
				</second>
			</item>
			<item>
				<first>357</first>
				<second>
					<qt_shared_ptr class_id_reference="14" object_id="_7">
						<category_id>357</category_id>
						<name>category_3</name>
						<description>desc_3</description>
						<list_blog>
							<count>0</count>
						</list_blog>
					</qt_shared_ptr>
				</second>
			</item>
		</list_category>
	</px>
</boost.shared_ptr-blog->
[QxOrm] end dump 'boost::shared_ptr<blog>'

Introspection - Reflection

All C++ classes registered in QxOrm context (with qx::register_class<T>() function) can be used by introspection engine (or reflection engine) of QxOrm library. Introspection engine provides dynamically (so during program execution) some information about types. These information are called meta-datas and list some 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. For more details about introspection (or reflection), please read the Wikipedia web page.

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

Introspection engine of QxOrm library provides :
Note : QxService module of QxOrm library (click here to go to the tutorial) is based on introspection engine to provide an easy and powerful way to create C++ application server calling dynamically services methods (client request) on server side, and creating automatically input/ouput services parameters instances.

Other note : you can add extra information to introspection engine using property bag pattern. Indeed, qx::IxClass, qx::IxDataMember and qx::IxFunction classes contain a list of QVariant items associated to a QString key (read qx::QxPropertyBag class documentation for more details).

Other note : to initialize QxOrm introspection engine, it is recommanded to call following function once in your main for example :

// Following command is recommanded to initialize QxOrm introspection engine
qx::QxClassX::registerAllClasses(true);


Get a data member value dynamically

To get dynamically a data member value using introspection engine of QxOrm library, you have to work with qx::IxDataMember base class (interface). qx::IxDataMember class provides several methods to get a data member value (each method has a generic pointer void * as parameter which is the address of the current instance) : For example : we have a generic pointer void * to a person class. We can get the QString value of firstName property writing :

// Generic pointer of type void * : we know that p is of type 'person'
void * p = ...;

// Get a pointer to the registered data member 'firstName' of class 'person'
qx::IxDataMember * pDataMember = qx::QxClassX::getDataMember("person", "firstName");

// First method to get the data member value with the real type
QString sFirstName = pDataMember->getValue<QString>(p);

// Second method to get the data member value converted in QVariant
QVariant vFirstName = pDataMember->toVariant(p);

// Third method to get the value encapsulated in qx::any type
boost::any aFirstName = pDataMember->getValueAnyPtr(p);

// Check if all values are equals
qAssert((sFirstName == vFirstName.toString()) && (sFirstName == (* boost::any_cast<QString *>(aFirstName))));


Set a data member value dynamically

qx::IxDataMember base class (interface) is able to set dynamically a new value to a property class (modify its value). qx::IxDataMember class provides 2 methods (each method has a generic pointer void * as parameter which is the address of the current instance, and the new value to change) : For example : we have a generic pointer void * to a person class. We can modify firstName property of QString type writing :

// Generic pointer of type void * : we know that p is of type 'person'
void * p = ...;

// Get a pointer to the registered data member 'firstName' of class 'person'
qx::IxDataMember * pDataMember = qx::QxClassX::getDataMember("person", "firstName");

// First method to change the data member value
QVariant vFirstName = QVariant("my new firstname 1");
pDataMember->fromVariant(p, vFirstName);

// Other method to change the data member value (using real type)
QString sFirstName = "other firstname 2";
pDataMember->setValue<QString>(p, sFirstName);


Call function dynamically

Like data members (class properties), it is possible to register class methods (functions) in QxOrm context (support static and non static methods). Introspection engine of QxOrm library can invoke dynamically class methods. All functions registered in QxOrm context are associated to a qx::IxFunction instance. To register a class method in QxOrm context, you have to use these functions : For example : we want to register in QxOrm context several methods of a person class :

* person.h file :
#ifndef _PERSON_H_
#define _PERSON_H_

class person
{
public:
   long id;
   QString firstName;
   QString lastName;
   QDateTime birthDate;

   person() : id(0) { ; }
   virtual ~person() { ; }

   long getId() const;
   void myMethodWith2Params(int param1, const QString & param2);

   static double myStaticMethodWith1Param(long param1);

};

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

#endif // _PERSON_H_

* person.cpp file :
namespace qx {
template <> void register_class(QxClass<person> & t)
{
  t.id(& person::id, "id");
  t.data(& person::firstName, "first_name");
  t.data(& person::lastName, "last_name");
  t.data(& person::birthDate, "birth_date");

  t.fct_0<long>(& person::getId, "getId");
  t.fct_2<void, int, const QString &>(& person::myMethodWith2Params, "myMethodWith2Params");

  t.fctStatic_1<double, long>(& person::myStaticMethodWith1Param, "myStaticMethodWith1Param");
}}


Once registered in QxOrm context, it is possible to call functions dynamically using qx::QxClassX::invoke() and qx::QxClassX::invokeStatic() :

   // Generic pointer of type void * : we know that p is of type 'person'
   void * p = ...;

   // Call method 'long getId() const' and get return value
   boost::any returnValue;
   qx::QxClassX::invoke("person", "getId", p, "", (& returnValue));
   long lId = boost::any_cast<long>(returnValue);

   // Call method 'myMethodWith2Params' with 2 parameters encapsulated in a string (default separator for parameters is character '|')
   // This way to pass parameters to the function works only if parameters are numeric or string
   // If parameters are more complex, then you have to encapsulate parameters in a list of qx::any, as shown below
   qx::QxClassX::invoke("person", "myMethodWith2Params", p, "36|my string param 2");

   // Call method 'myMethodWith2Params' with 2 parameters encapsulated in a list of qx::any : std::vector<qx::any>
   std::vector<boost::any> lstParams;
   int iParam1 = 36; lstParams.push_back(iParam1); // Parameter at position 1
   QString sParam2 = "my string param 2"; lstParams.push_back(sParam2); // Parameter at position 2
   qx::QxClassX::invoke("person", "myMethodWith2Params", p, lstParams);

   // Call static method 'myStaticMethodWith1Param' with 1 parameter and get return value
   qx::QxClassX::invokeStatic("person", "myStaticMethodWith1Param", "19", (& returnValue));
   double dValue = boost::any_cast<double>(returnValue);


Create a C++ instance dynamically

Introspection engine of QxOrm library is able to create class instances dynamically based on class name (QxFactory module, design pattern factory) using following functions : For example : QxService module of QxOrm library creates services instances dynamically (based on service name) to execute server routines automatically :

   qx::service::IxService * ptr = qx::create_nude_ptr<qx::service::IxService>(m_sServiceName);   


Iterate over all classes/properties registered in QxOrm context

Here is an example based on introspection engine of QxOrm library : how to iterate over all classes, properties and methods registered in 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;
}

If we execute the qx::QxClassX::dumpAllClasses() function with qxBlog tutorial, here are output logs :

[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

Services : transfer persistent data layer over network (QxService module)

QxService module of QxOrm library provides an easy and powerful way to create C++ application server (services with request from client and response from server). A tutorial is available on QxOrm web site to show how to work with QxService module. QxService module is based on introspection engine and serialization engine of QxOrm library to transfer persistent data layer over network and execute automatically server routines on server side.

Note : to enable QxService module, you have to define _QX_ENABLE_QT_NETWORK compilation option in QxOrm.pri (or QxOrm.cmake) configuration file. This compilation option adds a dependency to QxOrm library : QtNetwork provided by Qt framework.

Other note : QxEntityEditor application is deployed with QxEECppServicesExport plugin : this plugin generates automatically all C++ source code to transfer all project entities over network. A list of client/server methods are generated automatically (to manage CRUD operations) :
  • count() : client/server query to count entities (possibility to add a SQL query filter) ;
  • fetchById() : client/server query to fetch entity properties based on its identifier ;
  • fetchAll() : client/server query to fetch properties of all entities (mapped to a database table) ;
  • fetchByQuery() : client/server query to fetch properties of entities filtered by a SQL query ;
  • insert() : client/server query to insert entity values ;
  • update() : client/server query to update entity values ;
  • save() : client/server query to save entity values (insert or update) ;
  • deleteById() : client/server query to delete an entity based on its identifier ;
  • deleteAll() : client/server query to delete all entities (mapped to a database table) ;
  • deleteByQuery() : client/server query to delete entities filtered by a SQL query ;
  • destroyById() : client/server query to delete an entity based on its identifier (manage soft delete behaviour, logical delete) ;
  • destroyAll() : client/server query to delete all entities mapped to a database table (manage soft delete behaviour, logical delete) ;
  • destroyByQuery() : client/server query to delete entities filtered by a SQL query (manage soft delete behaviour, logical delete) ;
  • executeQuery() : client/server query to execute a custom SQL query or stored procedure ;
  • exist() : client/server query to check if an entity already exists based on its identifier ;
  • isValid() : client/server query to check entity validity (QxValidator module).
It is possible to add and to customize services generated by QxEntityEditor application.

The goal of this chapter is to show QxService module concepts :

Input/output service parameters (request/response)

Each function exposed by a service has input parameters (request from client) and output parameters (response from server). These input/output parameters must inherit from qx::service::IxParameter interface and must be registered in QxOrm context (with void qx::register_class<T> function).

For example : here is an example of input/output parameters generated by QxEntityEditor application based on blog class of qxBlog tutorial :

* blog.services.gen.h file :
namespace services {

typedef boost::shared_ptr<blog> blog_ptr;
typedef qx::QxCollection<long, blog_ptr> list_of_blog;
typedef boost::shared_ptr<list_of_blog> list_of_blog_ptr;

/* -- Service Input Parameters -- */

class QXBLOG_SERVICES_EXPORT blog_input : public qx::service::IxParameter
{

public:

   blog_input();
   virtual ~blog_input();

   long id;                   //!< Id to fetch or delete
   blog_ptr instance;         //!< Single instance to fetch, insert, update, delete or validate
   list_of_blog_ptr list;     //!< List of instances to fetch, insert, update, delete or validate
   qx_query query;            //!< Query to execute when fetching, updating or deleting
   QStringList columns;       //!< List of columns to fetch or update
   QStringList relations;     //!< List of relations to fetch

};

typedef boost::shared_ptr<services::blog_input> blog_input_ptr;

/* -- Service Output Parameters -- */

class QXBLOG_SERVICES_EXPORT blog_output : public qx::service::IxParameter
{

public:

   blog_output();
   virtual ~blog_output();

   blog_ptr instance;            //!< Single instance from server
   list_of_blog_ptr list;        //!< List of instances from server
   QSqlError error;              //!< If a SQL error occurred, this output parameter is not empty
   qx::QxInvalidValueX invalid;  //!< Check if a single instance (or a list of instances) is valid
   qx_query query;               //!< Query which contains all results
   long count;                   //!< Count how many items in database using a query or not
   qx_bool exist;                //!< Check if a single instance (or a list of instances) exist in database

};

typedef boost::shared_ptr<services::blog_output> blog_output_ptr;

} // namespace services

QX_REGISTER_COMPLEX_CLASS_NAME_HPP_QXBLOG_SERVICES(services::blog_input, qx::service::IxParameter, 0, services_blog_input)
QX_REGISTER_COMPLEX_CLASS_NAME_HPP_QXBLOG_SERVICES(services::blog_output, qx::service::IxParameter, 0, services_blog_output)

* blog.services.gen.cpp file :
QX_REGISTER_COMPLEX_CLASS_NAME_CPP_QXBLOG_SERVICES(services::blog_input, services_blog_input)
QX_REGISTER_COMPLEX_CLASS_NAME_CPP_QXBLOG_SERVICES(services::blog_output, services_blog_output)

namespace qx {

template <>
void register_class(QxClass<services::blog_input> & t)
{
   t.data(& services::blog_input::id, "id");
   t.data(& services::blog_input::instance, "instance");
   t.data(& services::blog_input::list, "list");
   t.data(& services::blog_input::query, "query");
   t.data(& services::blog_input::columns, "columns");
   t.data(& services::blog_input::relations, "relations");
}

template <>
void register_class(QxClass<services::blog_output> & t)
{
   t.data(& services::blog_output::instance, "instance");
   t.data(& services::blog_output::list, "list");
   t.data(& services::blog_output::error, "error");
   t.data(& services::blog_output::invalid, "invalid");
   t.data(& services::blog_output::query, "query");
   t.data(& services::blog_output::count, "count");
   t.data(& services::blog_output::exist, "exist");
}

} // namespace qx


Note : input/output parameters can contain complex structures (containers, smart-pointers, etc...). So it is very easy to transfer complex classes (for example with relationships) with QxService module.

Define service functions exposed to clients

Each service registered in QxService module exposes a list of functions to clients (client/server queries). All services must inherit from qx::service::QxService<INPUT, OUTPUT> base class (INPUT and OUTPUT template parameters are explained in chapter : Input/output service parameters, request/response) and must be registered in QxOrm context (with void qx::register_class<T> function).

For example : here is a service example generated by QxEntityEditor application based on blog class of qxBlog tutorial :

* blog.services.gen.h file :
namespace services {

/* -- Service Definition -- */

typedef qx::service::QxService< blog_input, blog_output > blog_base_class;
class QXBLOG_SERVICES_EXPORT blog_services : public blog_base_class
{

   QX_REGISTER_FRIEND_CLASS(services::blog_services)

public:

   blog_services();
   virtual ~blog_services();

protected:

   void fetchById_();
   void fetchAll_();
   void fetchByQuery_();

   void insert_();
   void update_();
   void save_();
   void deleteById_();
   void deleteAll_();
   void deleteByQuery_();
   void destroyById_();
   void destroyAll_();
   void destroyByQuery_();

   void executeQuery_();
   void callQuery_();
   void exist_();
   void count_();
   void isValid_();

#ifdef _QXBLOG_SERVICES_MODE_CLIENT

public:

   blog_ptr fetchById(long id, const QStringList & columns = QStringList(), const QStringList & relations = QStringList());
   QSqlError fetchById(blog_ptr & p, const QStringList & columns = QStringList(), const QStringList & relations = QStringList());
   QSqlError fetchById(list_of_blog_ptr & lst, const QStringList & columns = QStringList(), const QStringList & relations = QStringList());
   QSqlError fetchAll(list_of_blog_ptr & lst, const QStringList & columns = QStringList(), const QStringList & relations = QStringList());
   QSqlError fetchByQuery(const qx_query & query, list_of_blog_ptr & lst, const QStringList & columns = QStringList(),
                                       const QStringList & relations = QStringList());

   QSqlError insert(blog_ptr & p, const QStringList & relations = QStringList());
   QSqlError insert(list_of_blog_ptr & lst, const QStringList & relations = QStringList());
   QSqlError update(blog_ptr & p, const qx_query & query = qx_query(),
                              const QStringList & columns = QStringList(), const QStringList & relations = QStringList());
   QSqlError update(list_of_blog_ptr & lst, const qx_query & query = qx_query(),
                              const QStringList & columns = QStringList(), const QStringList & relations = QStringList());
   QSqlError save(blog_ptr & p, const QStringList & relations = QStringList());
   QSqlError save(list_of_blog_ptr & lst, const QStringList & relations = QStringList());

   QSqlError deleteById(long id);
   QSqlError deleteById(blog_ptr & p);
   QSqlError deleteById(list_of_blog_ptr & lst);
   QSqlError deleteAll();
   QSqlError deleteByQuery(const qx_query & query);
   QSqlError destroyById(long id);
   QSqlError destroyById(blog_ptr & p);
   QSqlError destroyById(list_of_blog_ptr & lst);
   QSqlError destroyAll();
   QSqlError destroyByQuery(const qx_query & query);

   QSqlError executeQuery(qx_query & query, blog_ptr & p);
   QSqlError executeQuery(qx_query & query, list_of_blog_ptr & lst);
   QSqlError callQuery(qx_query & query);
   qx_bool exist(blog_ptr & p);
   qx_bool exist(list_of_blog_ptr & lst);
   QSqlError count(long & lCount, const qx_query & query = qx_query());
   qx::QxInvalidValueX isValid(blog_ptr & p);
   qx::QxInvalidValueX isValid(list_of_blog_ptr & lst);

#endif // _QXBLOG_SERVICES_MODE_CLIENT

};

typedef boost::shared_ptr<services::blog_services> blog_services_ptr;

} // namespace services

QX_REGISTER_COMPLEX_CLASS_NAME_HPP_QXBLOG_SERVICES(services::blog_services, qx::service::IxService, 0, services_blog_services)

* blog.services.gen.cpp file :
QX_REGISTER_COMPLEX_CLASS_NAME_CPP_QXBLOG_SERVICES(services::blog_services, services_blog_services)

namespace qx {

template <>
void register_class(QxClass<services::blog_services> & t)
{
   t.fct_0<void>(& services::blog_services::fetchById_, "fetchById");
   t.fct_0<void>(& services::blog_services::fetchAll_, "fetchAll");
   t.fct_0<void>(& services::blog_services::fetchByQuery_, "fetchByQuery");

   t.fct_0<void>(& services::blog_services::insert_, "insert");
   t.fct_0<void>(& services::blog_services::update_, "update");
   t.fct_0<void>(& services::blog_services::save_, "save");
   t.fct_0<void>(& services::blog_services::deleteById_, "deleteById");
   t.fct_0<void>(& services::blog_services::deleteAll_, "deleteAll");
   t.fct_0<void>(& services::blog_services::deleteByQuery_, "deleteByQuery");
   t.fct_0<void>(& services::blog_services::destroyById_, "destroyById");
   t.fct_0<void>(& services::blog_services::destroyAll_, "destroyAll");
   t.fct_0<void>(& services::blog_services::destroyByQuery_, "destroyByQuery");

   t.fct_0<void>(& services::blog_services::executeQuery_, "executeQuery");
   t.fct_0<void>(& services::blog_services::callQuery_, "callQuery");
   t.fct_0<void>(& services::blog_services::exist_, "exist");
   t.fct_0<void>(& services::blog_services::count_, "count");
   t.fct_0<void>(& services::blog_services::isValid_, "isValid");
}

} // namespace qx

// Then there is the implementation of all functions provided by the service...


Note : once registered in QxOrm context, all clients connected to server can call these functions exposed by the service : server routines are executed automatically. Data serialization and network layer to transfer persistent classes are managed automatically by QxService module.

List of options available on server side

C++ application server based on QxService module provides several parameters in qx::service::QxConnect singleton class :
  • setPort() : listening port number to receive request from client and send response from server ;
  • setThreadCount() : threads count available on server side to manage several client requests at the same time ;
  • setSerializationType() : serialization type used to send response from server to client ;
  • setCompressData() : define if data sent from server to client are compressed or not ;
  • setEncryptData() : define if data sent from server to client are encrypted or not (with possibility to configure an encryption key).

Connection settings on client side

Client layer based on QxService module provides several parameters in qx::service::QxConnect singleton class :
  • setIp() : IP address of C++ application server ;
  • setPort() : port number used by C++ application server ;
  • setSerializationType() : serialization type used by client layer to send requests from client to server ;
  • setCompressData() : define if data sent from client to server are compressed or not ;
  • setEncryptData() : define if data sent from client to server are encrypted or not (with possibility to configure an encryption key).

Service authentication

It is often necessary to add a control on server side to check users connected on client side. qx::service::IxService interface (base class for all services registered in QxService module) provides virtual methods which can be overridden to manage authentication :
  • onBeforeProcess() : virtual method called before server routine execution ;
  • onAfterProcess() : virtual method called after server routine execution.

For example : here is a class named ParameterAuthentication which can be used as base class for all other parameters, this class provides 3 properties login, password and token :

* ParameterAuthentication.h file :
class MY_DLL_EXPORT ParameterAuthentication : public qx::service::IxParameter
{
 
public:
 
   ParameterAuthentication();
   virtual ~ParameterAuthentication();
 
   QString login;
   QString password;
   QString token;
   // etc..., put here all properties required by the authentication process
 
};
 
typedef boost::shared_ptr<ParameterAuthentication> ParameterAuthentication_ptr;
 
QX_REGISTER_COMPLEX_CLASS_NAME_HPP_MY_DLL(ParameterAuthentication, qx::service::IxParameter, 0, ParameterAuthentication)

* ParameterAuthentication.cpp file :
QX_REGISTER_COMPLEX_CLASS_NAME_CPP_MY_DLL(ParameterAuthentication, ParameterAuthentication)
 
namespace qx {
 
template <>
void register_class(QxClass<ParameterAuthentication> & t)
{
   t.data(& ParameterAuthentication::login, "login");
   t.data(& ParameterAuthentication::password, "password");
   t.data(& ParameterAuthentication::token, "token");
}
 
} // namespace qx


We have a base class for all parameters (ParameterAuthentication), we will now create a base class for all services named ServiceAuthentication<INPUT, OUTPUT>. This service base class will override onBeforeProcess() virtual method to manage authentication before each service routine execution :

* ServiceAuthentication.h file :
#include "ParameterAuthentication.h"
 
template <class INPUT, class OUTPUT>
class ServiceAuthentication : public qx::service::QxService<INPUT, OUTPUT>
{
 
public:
 
   ServiceAuthentication(const QString & sServiceName) : qx::service::QxService<INPUT, OUTPUT>(sServiceName) { ; }
   virtual ~ServiceAuthentication() { ; }
 
   virtual void onBeforeProcess()
   {
      // Here you can implement your own authentication control (checking login/password for example)
      // You can get input authentication parameters like this :
      ParameterAuthentication_ptr pParams = getInputParameter();
      pParams->login, pParams->password, etc...
 
      // If authentication is not valid, then you can throw an exception (and stop process before executing service function)
      throw qx::exception("Authentication error !");
   }
 
};


Now we have ParameterAuthentication base class and ServiceAuthentication<INPUT, OUTPUT> base class : all parameters and services must inherit from these classes to manage automatically authentication, and return an error message to client when user settings are not valid.

Note : like authentication, it is possible to manage logs on server side using onBeforeProcess() and onAfterProcess() virtual methods.

Async client/server queries

By default, all client/server queries are synchronous operations : that means that client layer waits for server response to continue its execution. With a user interface (GUI), a client/server query locks application (freeze) if it is executed in the main thread : so if server response is not sent quickly, users could think that the application is crashed. QxService module provides an easy way to perform asynchronous client/server queries (so without freezing GUI user interface) with qx::service::QxClientAsync class.

qx::service::QxClientAsync class is based on introspection engine of QxOrm library and Qt SIGNAL-SLOT feature. qx::service::QxClientAsync class requires :
  • a service instance ;
  • input/output service parameters ;
  • server routine name to execute (string format) ;
  • a callback function called at the end of the transaction (connection to finished() signal event).

Here is an example from qxClientServer tutorial which runs a server routine asynchronously :

void main_dlg::onClickBtnDateTimeAsync()
{
   if (m_pDateTimeAsync) { qDebug("[QxOrm] '%s' transaction is already running", "server_infos::get_current_date_time"); return; }

   // Création d'une instance de service et appel à la méthode pour recevoir la date-heure courante du serveur (mode asynchrone)
   server_infos_ptr service = server_infos_ptr(new server_infos());
   m_pDateTimeAsync.reset(new qx::service::QxClientAsync());
   QObject::connect(m_pDateTimeAsync.get(), SIGNAL(finished()), this, SLOT(onDateTimeAsyncFinished()));
   m_pDateTimeAsync->setService(service, "get_current_date_time");
   m_pDateTimeAsync->start();
}

void main_dlg::onDateTimeAsyncFinished()
{
   if (! m_pDateTimeAsync || ! m_pDateTimeAsync->getService()) { return; }
   updateLastTransactionLog(m_pDateTimeAsync->getService()->getTransaction());
   m_pDateTimeAsync.reset();
}


Note : above example shows how to perform an asynchronous client/server query with these steps :
  • create a service instance (of server_infos_ptr type in this example) ;
  • create a qx::service::QxClientAsync instance ;
  • connect finished event to a callback function (named onDateTimeAsyncFinished() in this example) ;
  • pass service instance and service function name to execute to qx::service::QxClientAsync object ;
  • run the transaction calling start() method.

Model View engine (QxModelView module)

QxModelView module provides an easy way to work with Qt model/view engine with all C++ classes registered in QxOrm context :
  • QML : each property defined in QxOrm context is exposed to QML engine : QxModelView module makes easier integration between QML and databases ;
  • Qt widgets : QTableView or QListView for example to display/modify a database table content.
qx::IxModel interface provides a generic way for all models linked to persistent classes registered in QxOrm context. All methods of this class prefixed by 'qx' call functions from qx::dao namespace and then communicate with database. qx::IxModel interface provides also Q_INVOKABLE methods which can be called in QML files :
  • qxCount_() : entities count in table mapped to model (possibility to add a SQL query filter) ;
  • qxFetchById_() : fetch model properties based on its identifier ;
  • qxFetchAll_() : fetch model with all entities in table mapped to model ;
  • qxFetchByQuery_() : fetch model with entities in table mapped to model filtered by a SQL query ;
  • qxFetchRow_() : fetch (update) a model row (each model row provides its own identifier) ;
  • qxInsert_() : insert all model entities (all model rows) to database ;
  • qxInsertRow_() : insert a model row to database ;
  • qxUpdate_() : update all model entities (all model rows) to database ;
  • qxUpdateRow_() : update a model row to database ;
  • qxSave_() : save all model entities (all model rows) to database (insert or update) ;
  • qxSaveRow_() : save a model row to database (insert or update) ;
  • qxDeleteById_() : delete an entity from database based on the identifier parameter ;
  • qxDeleteAll_() : delete all entities in table mapped to model ;
  • qxDeleteByQuery_() : delete entities in table mapped to model based on a SQL query ;
  • qxDeleteRow_() : delete a model row in database (each model row provides its own identifier) ;
  • qxDestroyById_() : delete an entity from database based on the identifier parameter (support soft delete behaviour, logical delete) ;
  • qxDestroyAll_() : delete all entities in table mapped to model (support soft delete behaviour, logical delete) ;
  • qxDestroyByQuery_() : delete entities in table mapped to model based on a SQL query (support soft delete behaviour, logical delete) ;
  • qxDestroyRow_() : delete a model row in database (each model row provides its own identifier), support soft delete behaviour, logical delete ;
  • qxExecuteQuery_() : fetch model using a custom SQL query or stored procedure ;
  • qxExist_() : check if an entity already exists based on the identifier parameter ;
  • qxValidate_() : check validity of all model content (QxValidator module) ;
  • qxValidateRow_() : check validity of a model row (QxValidator module).

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

Simple model (without relationship)

All classes registered in QxOrm context can be used as a model to display/modify values in views. qx::IxModel QxOrm model base class inherits from Qt QAbstractItemModel base class : so QxOrm models are full compatible with Qt model/view engine.

Only 1 line in C++ source code to instantiate a QxOrm model :

   qx::IxModel * pModel = new qx::QxModel<MyClass>();   

Note : the QxOrm model created with this line of code exposes automatically all properties registered in QxOrm context to Qt model/view engine.

Model with relationships (nested models)

Associate class relationships (1-n, n-1 and n-n) to Qt model/view engine is complex : the solution provided by QxOrm library is based on nested models concept. For more details about nested models concept, a french tutorial is available on famous developpez.com forum.

To use relationships (1-n, n-1 and n-n) with QxModelView module, it is very important to understand that there is a hierarchy between models (a parent model can be associated to several child models, this is the nested models concept).

To be able to work with relationships (nested models), it is necessary to create derived classes based on qx::QxModel<T> base class. This way, all simple properties (not relationship) are automatically exposed to views (thanks to the base class), the only thing to do is to write accessors to manage relationships. QxEntityEditor application is deployed with QxEECppModelViewExport plugin : this plugin generates source code automatically to work with nested models.

Here is a source code example generated by QxEntityEditor application to create a QxOrm model based on blog class (read qxBlog tutorial for more details). blog class defines 3 relationships : author (n-1), list_of_comment (1-n) and list_of_category (n-n) :

* blog.model_view.gen.h file :
namespace model_view {

typedef qx::QxModel<blog> blog_model_base_class;

class QXBLOG_MODEL_VIEW_EXPORT blog_model : public blog_model_base_class
{

   Q_OBJECT

public:

   blog_model(QObject * parent = 0);
   blog_model(qx::IxModel * other, QObject * parent);
   virtual ~blog_model();

   Q_INVOKABLE QObject * author(int row, bool bLoadFromDatabase = false, const QString & sAppendRelations = QString());
   Q_INVOKABLE QObject * list_of_comment(int row, bool bLoadFromDatabase = false, const QString & sAppendRelations = QString());
   Q_INVOKABLE QObject * list_of_category(int row, bool bLoadFromDatabase = false, const QString & sAppendRelations = QString());

   /* List of properties exposed by the model (3) :
      - blog_id
      - title
      - text
   */

protected:

   virtual void syncNestedModel(int row, const QStringList & relation);
   virtual void syncAllNestedModel(const QStringList & relation);

};

} // namespace model_view

* blog.model_view.gen.cpp file :
namespace model_view {

blog_model::blog_model(QObject * parent /* = 0 */) : blog_model_base_class(parent) { ; }

blog_model::blog_model(qx::IxModel * other, QObject * parent) : blog_model_base_class(other, parent) { ; }

blog_model::~blog_model() { ; }

QObject * blog_model::author(int row, bool bLoadFromDatabase /* = false */, const QString & sAppendRelations /* = QString() */)
{
   QString sRelation = "author";
   qx::IxModel * pChild = (bLoadFromDatabase ? NULL : this->getChild(row, sRelation));
   if (pChild) { return static_cast<QObject *>(pChild); }

   if ((row < 0) || (row >= this->m_model.count())) { qAssert(false); return NULL; }
   blog_model_base_class::type_ptr ptr = this->m_model.getByIndex(row);
   if (! ptr) { qAssert(false); return NULL; }
   long id = ptr->getblog_id();
   blog::type_author value = ptr->getauthor();

   if (bLoadFromDatabase)
   {
      if (! sAppendRelations.isEmpty() && ! sAppendRelations.startsWith("->") && ! sAppendRelations.startsWith(">>")) { sRelation += "->" + sAppendRelations; }
      else if (! sAppendRelations.isEmpty()) { sRelation += sAppendRelations; }
      blog tmp;
      tmp.setblog_id(id);
      this->m_lastError = qx::dao::fetch_by_id_with_relation(sRelation, tmp);
      if (this->m_lastError.isValid()) { return NULL; }
      value = tmp.getauthor();
      ptr->setauthor(value);
   }

   model_view::author_model * pNewChild = NULL;
   pChild = qx::model_view::create_nested_model_with_type(this, QModelIndex(), value, pNewChild);
   if (pChild) { this->insertChild(row, "author", pChild); }
   return static_cast<QObject *>(pChild);
}

QObject * blog_model::list_of_comment(int row, bool bLoadFromDatabase /* = false */, const QString & sAppendRelations /* = QString() */)
{
   QString sRelation = "list_of_comment";
   qx::IxModel * pChild = (bLoadFromDatabase ? NULL : this->getChild(row, sRelation));
   if (pChild) { return static_cast<QObject *>(pChild); }

   if ((row < 0) || (row >= this->m_model.count())) { qAssert(false); return NULL; }
   blog_model_base_class::type_ptr ptr = this->m_model.getByIndex(row);
   if (! ptr) { qAssert(false); return NULL; }
   long id = ptr->getblog_id();
   blog::type_list_of_comment value = ptr->getlist_of_comment();

   if (bLoadFromDatabase)
   {
      if (! sAppendRelations.isEmpty() && ! sAppendRelations.startsWith("->") && ! sAppendRelations.startsWith(">>")) { sRelation += "->" + sAppendRelations; }
      else if (! sAppendRelations.isEmpty()) { sRelation += sAppendRelations; }
      blog tmp;
      tmp.setblog_id(id);
      this->m_lastError = qx::dao::fetch_by_id_with_relation(sRelation, tmp);
      if (this->m_lastError.isValid()) { return NULL; }
      value = tmp.getlist_of_comment();
      ptr->setlist_of_comment(value);
   }

   model_view::comment_model * pNewChild = NULL;
   pChild = qx::model_view::create_nested_model_with_type(this, QModelIndex(), value, pNewChild);
   if (pChild) { this->insertChild(row, "list_of_comment", pChild); }
   return static_cast<QObject *>(pChild);
}

QObject * blog_model::list_of_category(int row, bool bLoadFromDatabase /* = false */, const QString & sAppendRelations /* = QString() */)
{
   QString sRelation = "list_of_category";
   qx::IxModel * pChild = (bLoadFromDatabase ? NULL : this->getChild(row, sRelation));
   if (pChild) { return static_cast<QObject *>(pChild); }

   if ((row < 0) || (row >= this->m_model.count())) { qAssert(false); return NULL; }
   blog_model_base_class::type_ptr ptr = this->m_model.getByIndex(row);
   if (! ptr) { qAssert(false); return NULL; }
   long id = ptr->getblog_id();
   blog::type_list_of_category value = ptr->getlist_of_category();

   if (bLoadFromDatabase)
   {
      if (! sAppendRelations.isEmpty() && ! sAppendRelations.startsWith("->") && ! sAppendRelations.startsWith(">>")) { sRelation += "->" + sAppendRelations; }
      else if (! sAppendRelations.isEmpty()) { sRelation += sAppendRelations; }
      blog tmp;
      tmp.setblog_id(id);
      this->m_lastError = qx::dao::fetch_by_id_with_relation(sRelation, tmp);
      if (this->m_lastError.isValid()) { return NULL; }
      value = tmp.getlist_of_category();
      ptr->setlist_of_category(value);
   }

   model_view::category_model * pNewChild = NULL;
   pChild = qx::model_view::create_nested_model_with_type(this, QModelIndex(), value, pNewChild);
   if (pChild) { this->insertChild(row, "list_of_category", pChild); }
   return static_cast<QObject *>(pChild);
}

void blog_model::syncNestedModel(int row, const QStringList & relation)
{
   Q_UNUSED(relation);
   qx::IxModel * pNestedModel = NULL;
   if ((row < 0) || (row >= this->m_model.count())) { return; }
   blog_model_base_class::type_ptr ptr = this->m_model.getByIndex(row);
   if (! ptr) { return; }

   pNestedModel = this->getChild(row, "author");
   if (pNestedModel)
   {
      this->syncNestedModelRecursive(pNestedModel, relation);
      blog::type_author value;
      qx::model_view::sync_nested_model(pNestedModel, value);
      ptr->setauthor(value);
   }

   pNestedModel = this->getChild(row, "list_of_comment");
   if (pNestedModel)
   {
      this->syncNestedModelRecursive(pNestedModel, relation);
      blog::type_list_of_comment value;
      qx::model_view::sync_nested_model(pNestedModel, value);
      ptr->setlist_of_comment(value);
   }

   pNestedModel = this->getChild(row, "list_of_category");
   if (pNestedModel)
   {
      this->syncNestedModelRecursive(pNestedModel, relation);
      blog::type_list_of_category value;
      qx::model_view::sync_nested_model(pNestedModel, value);
      ptr->setlist_of_category(value);
   }
}

void blog_model::syncAllNestedModel(const QStringList & relation)
{
   if (this->m_lstChild.count() <= 0) { return; }
   for (long l = 0; l < this->m_model.count(); l++)
   { this->syncNestedModel(static_cast<int>(l), relation); }
}

} // namespace model_view


Note : above example shows that the source code required to work with nested models is verbose. So to be able to work with models and relationships, it is strongly recommended to use QxEntityEditor application to generate all C++ source code automatically.

Interaction with QML views

Here is an example in QML (with Qt5, QxModelView module supports Qt4 too). This example uses 'author' table defined in qxBlog tutorial (source code of this QML example is available in qxBlogModelView project sample of QxOrm package) :

// 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();

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, following window should be displayed :

qx_model_view_02

Note : 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 in 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.

Other note : a QxEntityEditor plugin generates automatically source code to manage relationships using nested models concept (for more details about nested models concept, please read this french tutorial on famous developpez.com web site).

Interaction with QtWidget views

Here is an example to display/modify data from 'author' table (read qxBlog tutorial for 'author' class definition) in a QTableView (source code of this example is available in qxBlogModelView project sample of QxOrm package) :

// 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, following window should be displayed :

qx_model_view_01

Note : Qt provides several QtWidget views which can be mapped to a model, for example : QListView, QTableView, QTreeView. It is also possible to use QDataWidgetMapper class to create your own form based on a model (a french tutorial is available on developpez.com web site).

Connect model to QxService module

QxModelView module provides qx::QxModelService<T, S> class template (which inherits from : qx::QxModel<T> >> qx::IxModel >> QAbstractItemModel). This class has 2 template parameters :
  • T : class registered in QxOrm context with all properties exposed to Qt model/view engine ;
  • S : service class of QxService module to access/modify data from model (client/server requests).
Data provided by this model comes from client/server requests thanks to QxService module (so data are not received from a database SQL query). The S service class must provide some methods :
  • count() : client/server request to count entities in table mapped to model (with possibility to add a SQL query filter) ;
  • fetchById() : client/server request to fetch model properties based on identifier parameter ;
  • fetchAll() : client/server request to fetch model with all entities in table mapped to model ;
  • fetchByQuery() : client/server request to fetch model with entities in table mapped to model filtered by a SQL query ;
  • insert() : client/server request to insert model data to database ;
  • update() : client/server request to update model data to database ;
  • save() : client/server request to save model data to database (insert or update) ;
  • deleteById() : client/server request to delete an entity based on identifier parameter ;
  • deleteAll() : client/server request to delete all entities in table mapped to model ;
  • deleteByQuery() : client/server request to delete entities in table mapped to model based on a SQL query ;
  • destroyById() : client/server request to delete an entity based on identifier parameter (support soft delete behaviour, logical delete) ;
  • destroyAll() : client/server request to delete all entities in table mapped to model (support soft delete behaviour, logical delete) ;
  • destroyByQuery() : client/server request to delete entities in table mapped to model based on a SQL query (support soft delete behaviour, logical delete) ;
  • executeQuery() : client/server request to execute a custom SQL query or stored procedure ;
  • exist() : client/server request to check if model exists based on its identifier ;
  • isValid() : client/server request to check model validity (QxValidator module).

Note : QxEntityEditor application is deployed with QxEECppServicesExport and QxEECppModelViewExport plugins : these plugins generate automatically all C++ source code required to work with QxOrm models and the QxService module. So to use qx::QxModelService<T, S> class, it is strongly recommended to use QxEntityEditor application to generate source code automatically.

QxOrm and MongoDB database (C++ ODM Object Document Mapper)

QxOrm library is able to connect to standard relational databases (MySQL, PostgreSQL, SQLite, Oracle, Microsoft SQLServer, MariaDB, etc...), and is also able to connect to the NoSQL MongoDB database.

From Wikipedia website : MongoDB is a free and open-source cross-platform document-oriented database program. Classified as a NoSQL database program, MongoDB uses JSON-like documents with schemas.

MongoDB database has several advantages compared to standard relational databases (non-exhaustive list) :
  • Schema-less : you don't have to maintain tables and columns (so you don't need to write some complex scripts to migrate your database from one version to another version). MongoDB Collections can contain Documents with different fields, of different sizes, etc... About QxOrm library, that means that you can write your C++ persistent classes without having to deal with DDL database schema (useful in AGILE development environment for example) ;
  • Data are stored in BSON format (similar to JSON) : easy to read even with complex data structures ;
  • Powerful and flexible JSON query engine with possibility to put indexes on any fields of MongoDB Documents ;
  • MongoDB database is free of charge, and provides a support for professionals ;
  • From MongoDB version 3.6 : MongoDB query engine provides a way to simulate JOINS queries (between Documents) like standard relational databases.

QxOrm library API is the same for MongoDB database and any other standard relational databases. All QxOrm library features are available for MongoDB database : so everything in this user guide can be applied to MongoDB database. Main differences to take into account are :
  • It is recommended to define a C++ primary key of type QString. There is no numeric auto-incremented value : MongoDB provides an ObjectId type which can be mapped to QString C++ type and generated automatically (you can also create your own custom C++ type to map to MongoDB ObjectId).
  • Queries are not SQL : MongoDB provides a JSON query engine.

Note : QxOrm package provides a sample project named qxBlogMongoDB (in ./test/ directory). This sample project shows how to connect and work with MongoDB database and QxOrm library.

Prerequisites : driver libmongoc and libbson

QxOrm library is based on QtSql module from Qt framework : this module doesn't provide connectors to MongoDB database. So QxOrm library requires 2 extra-dependencies to connect to MongoDB database :
A guide is available to install these libraries (libmongoc and libbson) on your development environment.

QxOrm.pri (or QxOrm.cmake) configuration file

Once libmongoc and libbson libraries are installed on your development environment, you have to enable _QX_ENABLE_MONGODB compilation option in QxOrm.pri (or QxOrm.cmake) configuration file.

#######################################
# MongoDB Driver Library Dependencies #
#######################################

# If you enable _QX_ENABLE_MONGODB option, then QxOrm library will be able to use mongoc driver to store all QxOrm registered classes in a MongoDB database
# When _QX_ENABLE_MONGODB compilation option is defined, you must provide following paths to manage mongoc library dependencies :
#  - a BSON_INCLUDE environment variable to define where bson library source code is located (or a QX_BSON_INCLUDE_PATH qmake variable)
#  - a MONGOC_INCLUDE environment variable to define where mongoc library source code is located (or a QX_MONGOC_INCLUDE_PATH qmake variable)
#  - a BSON_LIB environment variable to define where bson library is located (or a QX_BSON_LIB_PATH qmake variable)
#  - a MONGOC_LIB environment variable to define where mongoc library is located (or a QX_MONGOC_LIB_PATH qmake variable)


Note : once _QX_ENABLE_MONGODB compilation option is defined, you can build and execute qxBlogMongoDB sample project in ./test/ directory to validate your development environment with QxOrm and MongoDB.

Connection to MongoDB database

Here is an example of settings to connect to MongoDB database :

// Parameters to connect to MongoDB database
qx::QxSqlDatabase * pDatabase = qx::QxSqlDatabase::getSingleton();
pDatabase->setDriverName("QXMONGODB");
pDatabase->setDatabaseName("qxBlog");
pDatabase->setHostName("localhost");
pDatabase->setPort(27017);
pDatabase->setUserName("");
pDatabase->setPassword("");


Register a MongoDB persistent class (Collection) in QxOrm context (mapping)

Register a MongoDB persistent class in QxOrm context is similar to register a persistent class for any other standard relational databases. Here is a persistent class example from qxBlogMongoDB sample project :

File blog.h :
#ifndef _QX_BLOG_BLOG_H_
#define _QX_BLOG_BLOG_H_

#include "author.h"
#include "comment.h"
#include "category.h"

class QX_BLOG_DLL_EXPORT blog
{
public:
// -- properties
   QString        m_id;
   QString        m_text;
   QDateTime      m_dt_creation;
   author_ptr     m_author;
   list_comment   m_commentX;
   list_category  m_categoryX;
// -- contructor, virtual destructor
   blog() { ; }
   virtual ~blog() { ; }
};

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

typedef std::shared_ptr<blog> blog_ptr;
typedef std::vector<blog_ptr> list_blog;

#endif // _QX_BLOG_BLOG_H_

File blog.cpp :
#include "../include/precompiled.h"
#include "../include/blog.h"
#include <QxOrm_Impl.h>

QX_REGISTER_CPP_QX_BLOG(blog)

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

   t.data(& blog::m_text, "blog_text");
   t.data(& blog::m_dt_creation, "date_creation");
   t.data(& blog::m_categoryX, "list_category"); // Embedded relationship

   t.relationManyToOne(& blog::m_author, "author_id"); // Referenced relationship
   t.relationOneToMany(& blog::m_commentX, "list_comment", "blog_id"); // Referenced relationship
}}

Note : this example shows how to define :

Manage ObjectId (primary key)

It is recommended to define a C++ primary key of type QString. There is no numeric auto-incremented value : MongoDB provides an ObjectId type which can be mapped to QString C++ type and generated automatically (you can also create your own custom C++ type to map to MongoDB ObjectId).

Insert a C++ instance (Document) in MongoDB database

Here is an example to insert a document in MongoDB database with primary key generated automatically (MongoDB ObjectId) :

// Insert one author without id
author_ptr author_1 = std::make_shared<author>();
author_1->m_name = "author_1";
author_1->m_sex = author::male;
author_1->m_birthdate = QDate(1998, 07, 12);
daoError = qx::dao::insert(author_1);


Here is an example to insert a document in MongoDB database providing a custom primary key :

// Insert one author with a custom id
author_ptr author_2 = std::make_shared<author>();
author_2->m_id = "my_custom_id_author_2";
author_2->m_name = "author_2";
author_2->m_sex = author::female;
author_2->m_birthdate = QDate(2003, 02, 28);
daoError = qx::dao::insert(author_2);


Insert many C++ instances (list of Documents) in MongoDB database

Here is an example to insert several documents in MongoDB database :

// Insert many authors with/without ids
QList<author> authorX;
author author_3; author_3.m_name = "author_3"; author_3.m_sex = author::female; author_3.m_birthdate = QDate(1968, 05, 01);
author author_4; author_4.m_id = "my_custom_id_author_4"; author_4.m_name = "author_4"; author_4.m_sex = author::male;
author author_5; author_5.m_name = "author_5"; author_5.m_sex = author::female; author_5.m_birthdate = QDate(1978, 03, 03);
authorX.append(author_3); authorX.append(author_4); authorX.append(author_5);
daoError = qx::dao::insert(authorX);


Note : QxOrm library supports several C++ types to manage lists and collections.

Update a C++ instance (Document) in MongoDB database

Here is an example to update a document in MongoDB database :

// Update one author
author author_4;
author_4.m_id = "my_custom_id_author_4";
author_4.m_name = "author_4_modified";
daoError = qx::dao::update(author_4);


Update many C++ instances (list of Documents) in MongoDB database

Here is an example to update several documents in MongoDB database :

// Update many authors
QList<author> authorX;
author_3.m_name = "author_3_modified_twice"; authorX.append(author_3);
author_2->m_name = "author_2_modified"; authorX.append(* author_2);
author_1->m_name = "author_1_modified"; authorX.append(* author_1);
daoError = qx::dao::update(authorX);


Note : QxOrm library supports several C++ types to manage lists and collections.

Delete a C++ instance (Document) from MongoDB database

Here is an example to delete a document from MongoDB database :

// Delete one author by id
author_ptr pAuthor = std::make_shared<author>();
pAuthor->m_id = "my_custom_id_author_4";
daoError = qx::dao::delete_by_id(pAuthor);


Delete many C++ instances (list of Documents) from MongoDB database

Here is an example to delete several documents from MongoDB database by identifier (primary key) :

// Delete many authors by id
QList<author> authorX;
author_3.m_id = "id_author_3"; authorX.append(author_3);
author_2->m_id = "id_author_2"; authorX.append(* author_2);
author_1->m_id = "id_author_1"; authorX.append(* author_1);
daoError = qx::dao::delete_by_id(authorX);


Here is an example to delete several documents from MongoDB database by JSON query :

// Delete authors by query (all male)
qx_query query{ { "sex", author::male } };
daoError = qx::dao::delete_by_query<author>(query);


To delete all documents from author collection :

// Delete all authors
daoError = qx::dao::delete_all<author>();


Note : QxOrm library supports several C++ types to manage lists and collections.

Fetch a C++ instance (Document) from MongoDB database

Here is an example to fetch a document from MongoDB database by identifier (primary key) :

// Fetch one author by id
author_ptr pAuthor = std::make_shared<author>();
pAuthor->m_id = "my_custom_id_author_2";
daoError = qx::dao::fetch_by_id(pAuthor);


Fetch many C++ instances (list of Documents) from MongoDB database

Here is an example to fetch several documents from MongoDB database by identifier (primary key) :

// Fetch many authors by id
QList<author> authorX;
author_3.m_id = "id_author_3"; authorX.append(author_3);
author_2->m_id = "id_author_2"; authorX.append(* author_2);
author_1->m_id = "id_author_1"; authorX.append(* author_1);
daoError = qx::dao::fetch_by_id(authorX);


Here is an example to fetch several documents from MongoDB database by JSON query :

// Fetch many authors by query (only female)
list_author list_of_female_author;
qx_query query{ { "sex", author::female } };
daoError = qx::dao::fetch_by_query(query, list_of_female_author);


Here is an example to fetch all documents from author collection in MongoDB database :

// Fetch all authors
list_author allAuthors;
daoError = qx::dao::fetch_all(allAuthors);


Here is an example to fetch all documents from author collection in MongoDB database (providing which fields/columns to fetch) :

// Fetch all authors (with only 'date_creation' and 'name' properties)
list_author allAuthors;
QStringList columns = QStringList() << "date_creation" << "name";
daoError = qx::dao::fetch_all(allAuthors, NULL, columns);


Note : QxOrm library supports several C++ types to manage lists and collections.

JSON queries

The main difference between standard relational databases and MongoDB database is query format : instead of SQL, MongoDB provides a JSON query engine.

Using qx::QxSqlQuery class (or qx_query alias)

qx::QxSqlQuery class (or qx_query alias) used to build standard SQL queries is also able to build JSON queries for MongoDB database. This class is based on C++11 std::initializer_list feature to write C++ queries like JSON queries (similar syntax). Please note that you can also write your JSON query with a string (if your compiler doesn't support C++11 std::initializer_list feature for example). For example :

// Fetch many authors by query (only female)
list_author list_of_female_author;
qx_query query { { "sex", author::female } };
daoError = qx::dao::fetch_by_query(query, list_of_female_author);


Using MongoDB aggregation framework

MongoDB database provides a powerful aggregation framework to build queries. Here is an example to call this MongoDB aggregation engine with qx::QxSqlQuery class (or qx_query alias), the first constructor parameter must be equal to aggregate :

// Fetch by query using MongoDB aggregation framework (only female)
list_author list_of_female_author;
qx_query queryAggregate("aggregate",
               "[ { \"$match\" : { \"sex\" : " + QString::number(static_cast<int>(author::female)) + " } } ]");
daoError = qx::dao::fetch_by_query(queryAggregate, list_of_female_author);


Add 'sort', 'limit', 'skip', etc..., properties to JSON query

It is often required to limit data received from database, or to sort them. To manage these operations, MongoDB database provides projection. Here is an example of projection with qx::QxSqlQuery class (or qx_query alias), see the QStringList constructor parameter (or second constructor parameter with std::initializer_list) :

// Fetch by query (only female) adding 'sort', 'limit', 'skip', etc... commands (see second query QStringList parameter)
list_of_female_author.clear();
qx_query queryOpts(QStringList() << "{ \"sex\" : " + QString::number(static_cast(author::female)) + " }"
                              << "{ \"sort\" : { \"sex\" : -1 }, \"limit\" : 2 }");
daoError = qx::dao::fetch_by_query(queryOpts, list_of_female_author);


Execute a custom query

To execute a custom query in MongoDB database, QxOrm library provides the qx::dao::call_query() function. Query results can be converted to QVariantMap or QList<QVariantMap> (if query returns a cursor) to iterate over all database response. Here are some examples of custom queries :

// Drop database
qx_query dropDB("{ \"dropDatabase\" : 1 }");
QSqlError daoError = qx::dao::call_query(dropDB);


// Call a custom query and get JSON response as QVariantMap
qx_query customQuery("{ \"find\": \"author\", \"filter\": { } }");
daoError = qx::dao::call_query(customQuery); qAssert(! daoError.isValid());
QString responseCustomQuery = customQuery.response().toString();
QVariantMap responseCustomQueryAsJson;
qx::serialization::json::from_string(responseCustomQueryAsJson, responseCustomQuery);


// Call a custom query with cursor and get JSON response as QList<QVariantMap>
qx_query customQueryCursor("cursor", "{ \"find\": \"author\", \"filter\": { } }");
daoError = qx::dao::call_query(customQueryCursor); qAssert(! daoError.isValid());
QString responseCustomQueryCursor = customQueryCursor.response().toString();
QList<QVariantMap> responseCustomQueryCursorAsJson;
qx::serialization::json::from_string(responseCustomQueryCursorAsJson, responseCustomQueryCursor);


Relationships engine (MongoDB version 3.6 or + is required)

QxOrm library relationship engine supports MongoDB database (MongoDB version 3.6 or + is required). So QxOrm library is able to fetch Document fields over several Collections using only one query (similar to JOINS in SQL).

Here is an example to fetch a Document and 1 level of relationships (parent > children) :

// Fetch blog with all relations : 'author', 'comment' and 'category' (MongoDB version 3.6+ is required for relationships)
blog_ptr blog = std::make_shared<blog>();
blog->m_id = "id_blog_1";
daoError = qx::dao::fetch_by_id_with_all_relation(blog);


Here is an example to fetch a Document and 4 levels of relationships (using *->*->*->* syntax) :

// Fetch blog with many relations using "*->*->*->*" (4 levels of relationships)
blog_ptr blog = std::make_shared<blog>();
blog->m_id = "id_blog_1";
daoError = qx::dao::fetch_by_id_with_relation("*->*->*->*", blog);


Here is an example to fetch a Document providing a list of relationships and fields to fetch (using { <col_1>, <col_2>, etc... } syntax) :

// Fetch relations defining fields to fetch with syntax { col_1, col_2, etc... }
list_blog lstBlogComplexRelation;
QStringList relations = QStringList() << "{ blog_text }" << "author_id { name, birthdate }" << "list_comment { comment_text } -> blog_id -> *";
daoError = qx::dao::fetch_all_with_relation(relations, lstBlogComplexRelation);


Here is an example to fetch a Document providing a list of relationships and fields to not fetch (using -{ <col_1>, <col_2>, etc... } syntax) :

// Fetch relations defining columns to remove before fetching with syntax -{ col_1, col_2, etc... }
list_blog lstBlogComplexRelation2;
QStringList relations = QStringList() << "-{ blog_text }" << "author_id -{ name, birthdate }" << "list_comment -{ comment_text } -> blog_id -> *";
daoError = qx::dao::fetch_all_with_relation(relations, lstBlogComplexRelation2);


Embedded vs Referenced

One big advantage of MongoDB database is the possibility to store complex data structure (not limited by a 2 dimensions table/column structure like standard relational databases). A MongoDB Document can contain an objet and several sub-objects (hierarchy in Document structure). Include a sub-object in a Document has some advantages (no JOIN for example, so faster to fetch) and some disadvantages (a same object can be duplicated in database). So it is important to adopt the right strategy to store your data.

QxOrm library supports both :
  • Embedded relationship : the sub-object is included in the Document ;
  • Referenced relationship : create a JOIN like standard relational databases.

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

   t.data(& blog::m_text, "blog_text");
   t.data(& blog::m_dt_creation, "date_creation");
   t.data(& blog::m_categoryX, "list_category"); // Embedded relationship

   t.relationManyToOne(& blog::m_author, "author_id"); // Referenced relationship
   t.relationOneToMany(& blog::m_commentX, "list_comment", "blog_id"); // Referenced relationship
}}


Create automatically indexes

QxOrm library provides a way to generate indexes automatically (this function should be called at the beginning of your program, for example in the main) :
  • all indexes to manage relationships between Collections (to optimize JOINS) ;
  • all indexes defined by qx::IxDataMember::setIndex() method (in qx::register_class() function).

// To optimize queries : create automatically indexes based on relationships and properties marked as 'index'
daoError = qx::dao::mongodb::QxMongoDB_Helper::autoCreateIndexes(true);




HTTP/HTTPS web server (QxHttpServer module)

QxOrm library provides a standalone, multi-threaded and easy to use HTTP 1.1 web server named QxHttpServer module (based on QxService module). QxHttpServer module doesn't require any Apache or Nginx installation.

QxHttpServer module supports several features : Combined with QxRestApi module (which provides a JSON API to request your persistent data layer), QxHttpServer module is designed to develop modern web applications. For example, SPA (Single-Page Applications) web applications with famous Javascript frameworks like AngularJS, React, Meteor.js, etc...

Note : to enable QxHttpServer module, you have to define _QX_ENABLE_QT_NETWORK compilation option in QxOrm.pri (ou QxOrm.cmake) configuration file. _QX_ENABLE_QT_NETWORK compilation option adds a dependency to QtNetwork binary provided by Qt library.

Other note : QxOrm package contains a test project named qxBlogRestApi. This test project is a web application with several examples to request a persistent data layer from a web page (HTML and Javascript).

Hello World !

Here is a HTTP web server source code based on QxHttpServer module (this web server just returns Hello World ! to web client) :

#include <QtCore/qcoreapplication.h>
#include <QxOrm.h>

int main(int argc, char * argv[])
{
   QCoreApplication app(argc, argv);

   // HTTP server settings
   qx::service::QxConnect * serverSettings = qx::service::QxConnect::getSingleton();
   serverSettings->setPort(9642); // HTTP server listening port
   serverSettings->setKeepAlive(5000); // Keep-alive connection with client during 5s, then socket is disconnected and thread becomes available for other clients
   serverSettings->setThreadCount(50); // Number of threads waiting for client's requests,
                                                           // which means also how many requests can be handled simultaneously (in parallel) by HTTP server

   // Create a QxOrm HTTP server instance
   qx::QxHttpServer httpServer;

   // Define all HTTP server routes (dispatcher) to handle requests
   // Each callback is executed in a dedicated thread, so QxOrm HTTP server can handle several requests in parallel
   httpServer.dispatch("GET", "/", [](qx::QxHttpRequest & request, qx::QxHttpResponse & response) {
      response.data() = "Hello World !";
   });

   // Start HTTP server
   httpServer.startServer();

   // Start event loop
   return app.exec();
}


Result : open a web browser (Chrome, Firefox, Safari, Internet Explorer, Opera, etc...) and go to this URL : http://localhost:9642/. Your web browser should display :

QxHttpServer Hello World !

HTTP/HTTPS web server settings

HTTP web server settings are available with qx::service::QxConnect singleton class :
  • setPort() : web server listening port (default web server port is 80 but you can define what you want) ;
  • setThreadCount() : number of threads used by web server to handle HTTP requests (which means number of simultaneous clients managed by web server) ;
  • setMaxWait() : timeout in milli-seconds (for example to read/write on socket), -1 value means no timeout ;
  • setCompressData() : if HTTP client supports GZIP compression, then text responses (HTML / Javascript / CSS files, JSON stream, etc...) will be compressed as GZIP ;
  • setKeepAlive() : socket stay connected to client during X milli-seconds, -1 value means never disconnect ;
  • setSessionTimeOut() : timeout in milli-seconds before deleting unused sessions (server side storage per client).

Secured connections SSL/TLS

qx::service::QxConnect singleton class provides also some parameters to manage HTTPS secured connections (SSL and/or TLS).
Here is a secured connection settings example with server certificate and CA certificate authority (you can test this code with qxBlogRestApi project example) :

// Certificates created with this tutorial : https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/
QFile::copy(":/documents/cert_qxorm_ca.pem", appPath.filePath("files/cert_qxorm_ca.pem"));
QFile::copy(":/documents/cert_qxorm_server.crt", appPath.filePath("files/cert_qxorm_server.crt"));
QFile::copy(":/documents/cert_qxorm_server.key", appPath.filePath("files/cert_qxorm_server.key"));

QFile fileCertCA(appPath.filePath("files/cert_qxorm_ca.pem"));
fileCertCA.open(QIODevice::ReadOnly);
QList<QSslCertificate> certCA; certCA << QSslCertificate(fileCertCA.readAll());

QFile fileCertServerPublic(appPath.filePath("files/cert_qxorm_server.crt"));
fileCertServerPublic.open(QIODevice::ReadOnly);
QSslCertificate certServerPublic(fileCertServerPublic.readAll());

QFile fileCertServerPrivate(appPath.filePath("files/cert_qxorm_server.key"));
fileCertServerPrivate.open(QIODevice::ReadOnly);
QSslKey certServerPrivate(fileCertServerPrivate.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, "qxorm");

qx::service::QxConnect * serverSettings = qx::service::QxConnect::getSingleton();
serverSettings->setSSLEnabled(true);
serverSettings->setSSLCACertificates(certCA);
serverSettings->setSSLLocalCertificate(certServerPublic);
serverSettings->setSSLPrivateKey(certServerPrivate);


Note : by default, all SSL errors are ignored (often certificates errors). To manage your own security level, you can use following functions (from qx::service::QxConnect singleton class) :

Routing URL (dispatcher / endpoints)

QxHttpServer module provides an URL routing engine (dispatcher) to define functions (or lambda) to execute based on HTTP request parameters (HTTP method GET, POST, DELETE, etc... + URL).
Functions (or lambda) must be defined with this signature : void myRequestHandler(qx::QxHttpRequest & request, qx::QxHttpResponse & response);

qx::QxHttpServer class (or its qx_http_server alias) has following methods :
  • setCustomRequestHandler() : define a function (or lambda) executed if dispatcher doesn't find any other matched function ;
  • dispatch() : first parameter is HTTP method (GET, POST, DELETE, etc...), second parameter is requested URL (or its pattern), third parameter is function (or lambda) to execute ;
  • beforeDispatching() : function (or lambda) executed before handling HTTP request (can be used for example to log, or to manage an authentication process) ;
  • afterDispatching() : function (or lambda) executed after handling HTTP request (can be used for example to log) ;
  • clearDispatcher() : remove all routing rules from dispatcher (only function or lambda defined by setCustomRequestHandler() will be executed).
Note : dispatcher is thread-safe, so you can define URL routing rules even if web server is running.

Other note : each function (or lambda) is executed in its own thread. So QxOrm library HTTP web server can handle several HTTP requests simultaneously.

Example n°1 : this routing rule handles all HTTP requests with method GET + URL starts with /files/, and returns a static file content stored on server (QDir::currentPath() is static files root directory, and 5000 is chunked response size, this last parameter is optional) :

httpServer.dispatch("GET", "/files/*", [](qx::QxHttpRequest & request, qx::QxHttpResponse & response) {
   qx::QxHttpServer::buildResponseStaticFile(request, response, QDir::currentPath(), 5000);
});


Example n°2 : this routing rule handles all HTTP requests with method POST + URL is /qx, and calls QxRestApi module (which provides a JSON API to request persistent data layer). Example n°1 (static files) and example n°2 (QxRestApi module) are a good starting point to develop a SPA (Single-Page Applications) web application with famous Javascript frameworks like AngularJS, React, Meteor.js, etc...

httpServer.dispatch("POST", "/qx", [](qx::QxHttpRequest & request, qx::QxHttpResponse & response) {
   qx::QxHttpServer::buildResponseQxRestApi(request, response);
});


Example n°3 : this routing rule handles all HTTP requests with method GET + URL is /test_big_json, and builds a JSON response with an array of 10000 items :

httpServer.dispatch("GET", "/test_big_json", [](qx::QxHttpRequest & request, qx::QxHttpResponse & response) {
   // To compare with this benchmark : https://blog.binaryspaceship.com/2017/cpp-rest-api-frameworks-benchmark/
   // This is more a JSON benchmark than HTTP server benchmark (RapidJSON is faster than Qt QJson engine)
   QJsonArray arr; Q_UNUSED(request);
   for (int i = 0; i < 10000; ++i)
   {
      QJsonObject item;
      item.insert("id", QString::number(i));
      item.insert("name", QString("Hello World"));
      item.insert("type", QString("application"));
      arr.append(item);
   }
   QJsonDocument doc(arr);
   response.headers().insert("Content-Type", "application/json; charset=utf-8");
   response.data() = doc.toJson(QJsonDocument::Compact);
});


Note : dispatch() order is very important. The first item found by the dispatcher which matches requested URL is executed (all other dispatcher items are ignored). So you have to define first the most specific URL, and you have to define last the most generic URL (for example, pattern /* matches all URLs, so this is the most generic dispatcher item).

Dynamic URL routing

QxHttpServer dispatcher supports dynamic URL routing.
You can define some variables inside URL pattern with this syntax : <var_name:var_type> (var_type is optional, and can be equal to : int, long, float, double, string).

Dynamic URL routing is useful to define REST API.
For example, /blog/<blog_id:int> pattern + GET HTTP method can be used to fetch a blog based on its numeric unique identifier (fetch_by_id).

URL is a list of segments splitted by character /.
QxHttpServer dispatcher checks each segment from requested URL : if all segments match the pattern, then the function (or lambda) is executed.
To get dynamic variables values from URL, you must write : request.dispatchParams().value("var_name") (which returns a QVariant).

Example : this routing rule handles all HTTP requests with method GET + URL starts with /params/, followed by a segment which will contain the value of var1 variable, followed by a numeric segment which will contain the value of var2 variable. The lambda returns a HTTP response which displays the values of var1 and var2 variables from URL. If the web browser calls /params/abc/123/ URL then the function (or lambda) will be executed, BUT if the web browser calls /params/abc/def/ URL then the function (or lambda) won't be executed (because def is not numeric) and dispatcher will search another item to execute :

httpServer.dispatch("GET", "/params/<var1>/<var2:int>", [](qx::QxHttpRequest & request, qx::QxHttpResponse & response) {
   response.data() = "Test URL dispatch parameters :\r\n";
   response.data() += " - var1 = " + request.dispatchParams().value("var1").toByteArray() + "\r\n";
   response.data() += " - var2 = " + request.dispatchParams().value("var2").toByteArray() + "\r\n";
});


Note : you can also define a regular expression to route URLs with this syntax : <var_name:{my_reg_exp}>.

Get HTTP request parameters

qx::QxHttpRequest class (or its qx_http_request alias) contains all HTTP request parameters :
  • QUrl & url() : web browser requested URL ;
  • QString & command() : HTTP method (GET, POST, PUT, DELETE, etc...) ;
  • QString & version() : HTTP version provided by web browser (for example HTTP/1.1) ;
  • QByteArray & data() : HTTP request content ;
  • QByteArray header(const QByteArray & key) : get a HTTP header value provided by web browser (for example : request.header("Accept-Encoding")) ;
  • QxHttpCookie cookie(const QByteArray & name) : get a HTTP cookie provided by web browser ;
  • QString param(const QString & key) : get a HTTP parameter value (from URL, or from HTTP request content if 'content-type' is 'application/x-www-form-urlencoded') ;
  • QHash<QString, QVariant> & dispatchParams() : list of URL dynamic parameters computed by dispatcher (routing engine) ;
  • QString & sourceAddress() : IP address of client web browser ;
  • long & sourcePort() : port used by client web browser ;
  • QString guid() : internal HTTP request unique identifier (can be used to log for example).

Build HTTP response

qx::QxHttpResponse class (or its qx_http_response alias) is used to build HTTP response :
  • int & status() : HTTP response code (by default 200 which means OK) ;
  • QByteArray & data() : HTTP response content ;
  • QByteArray header(const QByteArray & key) : send a HTTP header to web browser (by default, some headers are created automatically : Server, Date, Content-Type and Connection) ;
  • QxHttpCookie cookie(const QByteArray & name) : send a HTTP cookie to web browser ;
  • qx_bool writeChunked(const QByteArray & data) : can be used to send chunked responses.

Sessions (storage per client on server side)

HTTP sessions are a way to store some data related to a client on server side. Session data are available for each client's requests (until session is expired). The first time server access to a client's session, a HTTP cookie with a unique identifier is generated and attached to HTTP response. Then all HTTP requests sent by client web browser will contain automatically a HTTP cookie with the same unique identifier. When a session is unused and expired, then it is deleted automatically.

qx::QxHttpSession class (or its qx_http_session alias) is a HTTP session on server side.
qx::QxHttpSessionManager singleton class must be used to access to a session :

httpServer.dispatch("GET", "/", [](qx::QxHttpRequest & request, qx::QxHttpResponse & response) {
   // If this is the first time to access to session, then a cookie is created automatically and attached to the response
   // Then each request sent by web browser will contain a cookie with the session id
   // The session expires on server side after qx::service::QxConnect::setSessionTimeOut() milliseconds
   qx::QxHttpSession_ptr session = qx::QxHttpSessionManager::getSession(request, response);
   if (session) { session->set("last_request_per_user", QDateTime::currentDateTime()); }
});


Note : qx::QxHttpSession class contains a hash-map (QHash<QByteArray, QVariant>) to store any values related to a client.

Other note : qx::service::QxConnect::setSessionTimeOut() method can be used to define a timeout (in milli-seconds) to delete unused sessions.

Cookies

From Wikipedia website : an HTTP cookie (also called web cookie, Internet cookie, browser cookie, or simply cookie) is a small piece of data sent from a website and stored on the user's computer by the user's web browser while the user is browsing. Cookies were designed to be a reliable mechanism for websites to remember stateful information (such as items added in the shopping cart in an online store) or to record the user's browsing activity (including clicking particular buttons, logging in, or recording which pages were visited in the past).

qx::QxHttpRequest and qx::QxHttpResponse classes provide the method cookies() to get cookies sent by web browser or generate some cookies in HTTP response. For example :

qx::QxHttpCookie cookie;
cookie.name = "my_http_cookie";
cookie.value = "my_value";
response.cookies().insert(cookie.name, cookie);


Note : an HTTP cookie is added automatically to HTTP response when accessing for the first time to a session (server side storage per client).

Static files

qx::QxHttpServer class (or its qx_http_server alias) provides a static method to send to client web browser a file content stored on server side (for example : HTML, Javascript, CSS, PNG, JPEG, videos, etc...).

httpServer.dispatch("GET", "/files/*", [](qx::QxHttpRequest & request, qx::QxHttpResponse & response) {
   qx::QxHttpServer::buildResponseStaticFile(request, response, QDir::currentPath(), 5000);
});

  • The third parameter (QDir::currentPath() in the example) defines the root directory where files are stored on the server ;
  • The fourth parameter (5000 in the example) is optional and defines chunked response size. This parameter can be useful to send big files (streaming).

Chunked responses

From Wikipedia website : Chunked transfer encoding is a streaming data transfer mechanism available in version 1.1 of the Hypertext Transfer Protocol (HTTP). In chunked transfer encoding, the data stream is divided into a series of non-overlapping "chunks". The chunks are sent out and received independently of one another. No knowledge of the data stream outside the currently-being-processed chunk is necessary for both the sender and the receiver at any given time. Each chunk is preceded by its size in bytes. The transmission ends when a zero-length chunk is received. The chunked keyword in the Transfer-Encoding header is used to indicate chunked transfer.

The introduction of chunked encoding in HTTP 1.1 provided various benefits :
  • Chunked transfer encoding allows a server to maintain an HTTP persistent connection for dynamically generated content.
  • Chunked encoding allows the sender to send additional header fields after the message body. This is important in cases where values of a field cannot be known until the content has been produced, such as when the content of the message must be digitally signed.

qx::QxHttpResponse class provides the qx_bool writeChunked(const QByteArray & data) method to send chunked response. It is used for example to send big static files (streaming) :

while (! file.atEnd())
{
   if (! response.writeChunked(file.read(chunkedSize))) { return; }
}


Note : the first response.writeChunked() call sends automatically all HTTP response headers. So you have to define all HTTP response headers before calling response.writeChunked().

Requests using JSON API (QxRestApi module)

QxRestApi module provides a generic JSON API to request your persistent data layer (CRUD operations, complex queries, several levels of relationships, custom JSON output format, call dynamically native C++ functions registered in QxOrm context, instance validation, call custom database queries).

This user manual has a full chapter dedicated to QxRestApi module : it contains several examples to request persistent data layer. Combining QxRestApi module and QxHttpServer module : you have all tools to develop modern web applications. For example, SPA (Single-Page Applications) web applications with famous Javascript frameworks like AngularJS, React, Meteor.js, etc...

Note : QxOrm package provides a test project named qxBlogRestApi. This project includes a HTTP web server developed with QxOrm library, and a client source code developed with HTML + Javascript (with jQuery).

For example, here is the Javascript function used to send JSON requests (POST method) from client web browser to QxOrm HTTP web server (all requests are sent to the same URL /qx) :

function sendRequest(request) {
   $.post("/qx", request, function(data, status, xhr) {
      $("#txtResponse").val(JSON.stringify(data, null, 3));
   }, "json").fail(function(error) {
      alert("An error occurred sending request to QxOrm HTTP server : " + error);
   });
}


On server side, handling these requests is very easy : qx::QxHttpServer class (or its qx_http_server alias) provides the qx::QxHttpServer::buildResponseQxRestApi() static method :

httpServer.dispatch("POST", "/qx", [](qx::QxHttpRequest & request, qx::QxHttpResponse & response) {
   qx::QxHttpServer::buildResponseQxRestApi(request, response);
});


Here is a JSON request example sent by client web browser to get the list of all blogs stored in database (fetch_all) :

{
   "request_id": "2b393e4c-a00c-45dc-a279-e9d76f1c55cf",
   "action": "fetch_all",
   "entity": "blog"
}


Here is the JSON response with the list of all blogs :

{
   "data": [
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": null,
            "list_blog": [],
            "name": "",
            "sex": 2
         },
         "blog_id": 1,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-03-27T20:51:23.107",
         "list_category": [],
         "list_comment": []
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": null,
            "list_blog": [],
            "name": "",
            "sex": 2
         },
         "blog_id": 2,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-03-27T20:51:23.107",
         "list_category": [],
         "list_comment": []
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": null,
            "list_blog": [],
            "name": "",
            "sex": 2
         },
         "blog_id": 3,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-03-27T20:51:23.107",
         "list_category": [],
         "list_comment": []
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": null,
            "list_blog": [],
            "name": "",
            "sex": 2
         },
         "blog_id": 4,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-03-27T20:51:23.107",
         "list_category": [],
         "list_comment": []
      }
   ],
   "request_id": "2b393e4c-a00c-45dc-a279-e9d76f1c55cf"
}


WebSocket

From Wikipedia website : WebSocket is a computer communications protocol, providing full-duplex communication channels over a single TCP connection. The WebSocket protocol enables interaction between a web browser (or other client application) and a web server with lower overheads, facilitating real-time data transfer from and to the server. This is made possible by providing a standardized way for the server to send content to the client without being first requested by the client, and allowing messages to be passed back and forth while keeping the connection open. In this way, a two-way ongoing conversation can take place between the client and the server.

QxOrm library is based on Qt framework which already provides a WebSocket implementation.
Create a web server with Qt WebSocket is very easy : there are several examples in Qt documentation.

So you can implement a web server like this :
  • a listening port for HTTP connections (using QxHttpServer module) ;
  • another listening port for WebSocket connections (using QtWebSockets module provided by Qt).

Note : a WebSocket connection is often created in Javascript code from client web browser, so having 2 listening ports (1 for HTTP, and another for WebSocket) is not a problem.

Performance (tested with Apache Benchmark)

Here is a performance test result with following parameters :
  • Operating System (OS) : Windows 2010 64bits ;
  • CPU : Intel Core i7-6820HQ @ 2.70GHz (laptop) ;
  • Qt version : 5.1.1 (release mode) ;
  • QxOrm version : 1.4.6 (built in release mode with Visual Studio 2012, default parameters, no specific optimization) ;
  • HTTP web server : qxBlogRestApi test project ;
  • Tool to execute the test : Apache Benchmark ;
  • Simulate 20000 requests with 50 concurrents connections simultaneously : ab -n 20000 -c 50 -k http://localhost:9642/params/abc/123

Test results show that QxOrm HTTP web server can handle more than 12000 requests per second :

QxHttpServer performance

Improve performance with epoll dispatcher on Linux

On Linux, you can improve HTTP web server performance using epoll to manage your sockets. By default, Qt framework is based on a slower process (select), but a method exists to define another event loop and dispatcher. Several libraries are available, for example :
qx::QxHttpServer class (or its qx_http_server alias) provides a method to define a custom epoll event dispatcher (you must call it before running the QxOrm HTTP web server) :

   httpServer.setEventDispatcher(new QEventDispatcherEpoll());   


JSON REST API (QxRestApi module)

QxRestApi module is a JSON API to manage (in a generic way) your persistent data layer (database) or call C++ native functions (registered in QxOrm context). QxRestApi module is based on a request/response mechanism : send a request in JSON format and receive a response in JSON format. QxRestApi module can be used for example to develop REST services.

QxRestApi module supports following features :
  • CRUD operations ;
  • complex queries with several levels of relationships ;
  • custom JSON output format ;
  • call dynamically C++ native functions registered in QxOrm context ;
  • instance validation ;
  • call custom database queries or stored procedures.

How it works

QxRestApi module is very easy to use : qx::QxRestApi class has only 1 main method : processRequest().
Prerequisites : all classes registered into QxOrm context must implement qx::IxPersistable interface.

A JSON query contains following properties :

{
   "request_id" : // [optional] unique identifier generated by client to associate response to request (if provided by caller, then the response will contain the same unique identifier)
   "action" : // [required] what is the action to execute on the server
   "entity" : // [optional or required depending on action] C++ class registered in QxOrm context
   "data" : // [optional or required depending on action] data in JSON format needed to execute action
   "columns" : // [optional] list of columns to fetch or update (if empty, means all columns)
   "relations" : // [optional] list of relationships to fetch or save
   "query" : // [optional or required depending on action] query to execute on database
   "output_format" : // [optional] output fields for the response (filter), if empty then response will contain all fields
   "fct" : // [required only with action 'call_entity_function'] used to call C++ native functions
   "save_mode" : // [optional] used only with action 'save' to define insert or update or check both insert/update
}


A JSON response contains following properties :

{
   "request_id" : // unique identifier generated by client's request (if any)
   "data" : // contain the response data
   "error" : // if an error occured, then contain a code and description of the error
}


Use cases

Several programming languages support natively JSON format (Javascript, PHP, Python, etc...). QxRestApi module provides an interoperability between QxOrm library and any other applications developed with another technology (not C++/Qt for example).

QxRestApi module can be useful for :

qxBlogRestApi example project (QML and HTTP web server)

QxOrm package contains a test project named qxBlogRestApi (in ./test/qxBlogRestApi/ directory).
This test project shows 2 ways to work with QxRestApi module :
  • The first screen is a QML application which uses embedded QML Javascript engine to request a persistent data layer or call C++ native functions :

    QxHttpServer performance


  • The second screen runs an HTTP web server based on QxHttpServer module, then opens default web browser (to load HTML + Javascript with jQuery) :

    QxHttpServer performance

These 2 windows are developed with different programming languages (QML versus HTML + Javascript), but provide exactly same features :
  • On top-left position : a text area to write a JSON request to send to QxRestApi module ;
  • Just below the JSON request text area : a button to send JSON request to QxRestApi module ;
  • On bottom-left position : a list of pre-loaded JSON requests examples (a click on this list fills automatically the JSON request text area) ;
  • On right side : a JSON response text area received from QxRestApi module (after executing a JSON request).

Fetch

This chapter provides several ways to get data from database (fetch) :

fetch_all

The fetch_all action gets all items from a table in database (and eventually several levels of relationships).

-- Example n°1 -- fetch all blogs (as list format) :

JSON request :
{
   "request_id": "5e988bac-c812-4cb1-b0d8-6a2c9dc4478b",
   "action": "fetch_all",
   "entity": "blog"
}
JSON response :
{
   "data": [
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": null,
            "list_blog": [],
            "name": "",
            "sex": 2
         },
         "blog_id": 1,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-04-01T16:18:54",
         "list_category": [],
         "list_comment": []
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": null,
            "list_blog": [],
            "name": "",
            "sex": 2
         },
         "blog_id": 2,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-04-01T16:18:54",
         "list_category": [],
         "list_comment": []
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": null,
            "list_blog": [],
            "name": "",
            "sex": 2
         },
         "blog_id": 3,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-04-01T16:18:54",
         "list_category": [],
         "list_comment": []
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": null,
            "list_blog": [],
            "name": "",
            "sex": 2
         },
         "blog_id": 4,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-04-01T16:18:54",
         "list_category": [],
         "list_comment": []
      }
   ],
   "request_id": "5e988bac-c812-4cb1-b0d8-6a2c9dc4478b"
}


-- Example n°2 -- fetch all blogs (as hash-map with key/value format) :

JSON request :
{
   "request_id": "ad400135-19fd-40e0-8034-201be6a2ff7a",
   "action": "fetch_all",
   "entity": "blog",
   "data": [
      {
         "key": "",
         "value": ""
      }
   ]
}
JSON response :
{
   "data": [
      {
         "key": 1,
         "value": {
            "author_id": {
               "author_id": "author_id_2",
               "birthdate": null,
               "list_blog": [],
               "name": "",
               "sex": 2
            },
            "blog_id": 1,
            "blog_text": "blog property 'text' modified => blog is dirty !!!",
            "date_creation": "2019-04-01T16:18:54",
            "list_category": [],
            "list_comment": []
         }
      },
      {
         "key": 2,
         "value": {
            "author_id": {
               "author_id": "author_id_2",
               "birthdate": null,
               "list_blog": [],
               "name": "",
               "sex": 2
            },
            "blog_id": 2,
            "blog_text": "blog property 'text' modified => blog is dirty !!!",
            "date_creation": "2019-04-01T16:18:54",
            "list_category": [],
            "list_comment": []
         }
      },
      {
         "key": 3,
         "value": {
            "author_id": {
               "author_id": "author_id_2",
               "birthdate": null,
               "list_blog": [],
               "name": "",
               "sex": 2
            },
            "blog_id": 3,
            "blog_text": "blog property 'text' modified => blog is dirty !!!",
            "date_creation": "2019-04-01T16:18:54",
            "list_category": [],
            "list_comment": []
         }
      },
      {
         "key": 4,
         "value": {
            "author_id": {
               "author_id": "author_id_2",
               "birthdate": null,
               "list_blog": [],
               "name": "",
               "sex": 2
            },
            "blog_id": 4,
            "blog_text": "blog property 'text' modified => blog is dirty !!!",
            "date_creation": "2019-04-01T16:18:54",
            "list_category": [],
            "list_comment": []
         }
      }
   ],
   "request_id": "ad400135-19fd-40e0-8034-201be6a2ff7a"
}


-- Example n°3 -- fetch all blogs and 2 levels of relationships :

JSON request :
{
   "request_id": "cf9ea2a8-3e41-438f-9a48-bbc8593d2b99",
   "action": "fetch_all",
   "entity": "blog",
   "relations": [
      "*->*"
   ]
}
JSON response :
{
   "data": [
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": "2019-04-01",
            "list_blog": [
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 2,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 3,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 4,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               }
            ],
            "name": "author name modified at index 1 => container is dirty !!!",
            "sex": 1
         },
         "blog_id": 1,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-04-01T16:18:54",
         "list_category": [
            {
               "key": 1,
               "value": {
                  "category_id": 1,
                  "description": "desc_1",
                  "list_blog": [
                     {
                        "key": 1,
                        "value": {
                           "author_id": {
                              "author_id": "author_id_2",
                              "birthdate": null,
                              "list_blog": [],
                              "name": "",
                              "sex": 2
                           },
                           "blog_id": 1,
                           "blog_text": "blog property 'text' modified => blog is dirty !!!",
                           "date_creation": "2019-04-01T16:18:54",
                           "list_category": [],
                           "list_comment": []
                        }
                     }
                  ],
                  "name": "category_1"
               }
            },
            {
               "key": 3,
               "value": {
                  "category_id": 3,
                  "description": "desc_3",
                  "list_blog": [
                     {
                        "key": 1,
                        "value": {
                           "author_id": {
                              "author_id": "author_id_2",
                              "birthdate": null,
                              "list_blog": [],
                              "name": "",
                              "sex": 2
                           },
                           "blog_id": 1,
                           "blog_text": "blog property 'text' modified => blog is dirty !!!",
                           "date_creation": "2019-04-01T16:18:54",
                           "list_category": [],
                           "list_comment": []
                        }
                     }
                  ],
                  "name": "category_3"
               }
            }
         ],
         "list_comment": [
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               "comment_id": 1,
               "comment_text": "comment_1 text",
               "date_creation": "2019-04-01T16:18:54"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               "comment_id": 3,
               "comment_text": "comment_1 text",
               "date_creation": "2019-04-01T16:18:54"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               "comment_id": 5,
               "comment_text": "comment_1 text",
               "date_creation": "2019-04-01T16:18:54"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               "comment_id": 7,
               "comment_text": "comment_1 text",
               "date_creation": "2019-04-01T16:18:54"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               "comment_id": 2,
               "comment_text": "comment_2 text",
               "date_creation": "2019-04-01T16:18:54"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               "comment_id": 4,
               "comment_text": "comment_2 text",
               "date_creation": "2019-04-01T16:18:54"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               "comment_id": 6,
               "comment_text": "comment_2 text",
               "date_creation": "2019-04-01T16:18:54"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               "comment_id": 8,
               "comment_text": "comment_2 text",
               "date_creation": "2019-04-01T16:18:54"
            }
         ]
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": "2019-04-01",
            "list_blog": [
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 2,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 3,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 4,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               }
            ],
            "name": "author name modified at index 1 => container is dirty !!!",
            "sex": 1
         },
         "blog_id": 2,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-04-01T16:18:54",
         "list_category": [
            {
               "key": 4,
               "value": {
                  "category_id": 4,
                  "description": "desc_1",
                  "list_blog": [
                     {
                        "key": 2,
                        "value": {
                           "author_id": {
                              "author_id": "author_id_2",
                              "birthdate": null,
                              "list_blog": [],
                              "name": "",
                              "sex": 2
                           },
                           "blog_id": 2,
                           "blog_text": "blog property 'text' modified => blog is dirty !!!",
                           "date_creation": "2019-04-01T16:18:54",
                           "list_category": [],
                           "list_comment": []
                        }
                     }
                  ],
                  "name": "category_1"
               }
            },
            {
               "key": 5,
               "value": {
                  "category_id": 5,
                  "description": "desc_3",
                  "list_blog": [
                     {
                        "key": 2,
                        "value": {
                           "author_id": {
                              "author_id": "author_id_2",
                              "birthdate": null,
                              "list_blog": [],
                              "name": "",
                              "sex": 2
                           },
                           "blog_id": 2,
                           "blog_text": "blog property 'text' modified => blog is dirty !!!",
                           "date_creation": "2019-04-01T16:18:54",
                           "list_category": [],
                           "list_comment": []
                        }
                     }
                  ],
                  "name": "category_3"
               }
            }
         ],
         "list_comment": []
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": "2019-04-01",
            "list_blog": [
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 2,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 3,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 4,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               }
            ],
            "name": "author name modified at index 1 => container is dirty !!!",
            "sex": 1
         },
         "blog_id": 3,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-04-01T16:18:54",
         "list_category": [
            {
               "key": 6,
               "value": {
                  "category_id": 6,
                  "description": "desc_1",
                  "list_blog": [
                     {
                        "key": 3,
                        "value": {
                           "author_id": {
                              "author_id": "author_id_2",
                              "birthdate": null,
                              "list_blog": [],
                              "name": "",
                              "sex": 2
                           },
                           "blog_id": 3,
                           "blog_text": "blog property 'text' modified => blog is dirty !!!",
                           "date_creation": "2019-04-01T16:18:54",
                           "list_category": [],
                           "list_comment": []
                        }
                     }
                  ],
                  "name": "category_1"
               }
            },
            {
               "key": 7,
               "value": {
                  "category_id": 7,
                  "description": "desc_3",
                  "list_blog": [
                     {
                        "key": 3,
                        "value": {
                           "author_id": {
                              "author_id": "author_id_2",
                              "birthdate": null,
                              "list_blog": [],
                              "name": "",
                              "sex": 2
                           },
                           "blog_id": 3,
                           "blog_text": "blog property 'text' modified => blog is dirty !!!",
                           "date_creation": "2019-04-01T16:18:54",
                           "list_category": [],
                           "list_comment": []
                        }
                     }
                  ],
                  "name": "category_3"
               }
            }
         ],
         "list_comment": []
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": "2019-04-01",
            "list_blog": [
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 2,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 3,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               },
               {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": null,
                     "list_blog": [],
                     "name": "",
                     "sex": 2
                  },
                  "blog_id": 4,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [],
                  "list_comment": []
               }
            ],
            "name": "author name modified at index 1 => container is dirty !!!",
            "sex": 1
         },
         "blog_id": 4,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-04-01T16:18:54",
         "list_category": [
            {
               "key": 8,
               "value": {
                  "category_id": 8,
                  "description": "desc_1",
                  "list_blog": [
                     {
                        "key": 4,
                        "value": {
                           "author_id": {
                              "author_id": "author_id_2",
                              "birthdate": null,
                              "list_blog": [],
                              "name": "",
                              "sex": 2
                           },
                           "blog_id": 4,
                           "blog_text": "blog property 'text' modified => blog is dirty !!!",
                           "date_creation": "2019-04-01T16:18:54",
                           "list_category": [],
                           "list_comment": []
                        }
                     }
                  ],
                  "name": "category_1"
               }
            },
            {
               "key": 9,
               "value": {
                  "category_id": 9,
                  "description": "desc_3",
                  "list_blog": [
                     {
                        "key": 4,
                        "value": {
                           "author_id": {
                              "author_id": "author_id_2",
                              "birthdate": null,
                              "list_blog": [],
                              "name": "",
                              "sex": 2
                           },
                           "blog_id": 4,
                           "blog_text": "blog property 'text' modified => blog is dirty !!!",
                           "date_creation": "2019-04-01T16:18:54",
                           "list_category": [],
                           "list_comment": []
                        }
                     }
                  ],
                  "name": "category_3"
               }
            }
         ],
         "list_comment": []
      }
   ],
   "request_id": "cf9ea2a8-3e41-438f-9a48-bbc8593d2b99"
}


-- Example n°4 -- fetch all blogs and some relationships + define an output JSON format (all properties will not be exported to JSON response) :

JSON request :
{
   "request_id": "4c45fdf9-8001-4509-bb4b-ce27a4a8708a",
   "action": "fetch_all",
   "entity": "blog",
   "relations": [
      "<blog_alias> { blog_text }",
      "author_id <author_alias> { name, birthdate }",
      "list_comment <list_comment_alias> { comment_text } -> blog_id <blog_alias_2> -> * <..._my_alias_suffix>"
   ],
   "output_format": [
      "{ blog_text }",
      "author_id { name, birthdate }",
      "list_comment { comment_text } -> blog_id -> *"
   ]
}
JSON response :
{
   "data": [
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": "2019-04-01",
            "name": "author name modified at index 1 => container is dirty !!!"
         },
         "blog_id": 1,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "list_comment": [
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": "2019-04-01",
                     "name": "author name modified at index 1 => container is dirty !!!",
                     "sex": 1
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [
                     {
                        "key": 1,
                        "value": {
                           "category_id": 1,
                           "description": "desc_1",
                           "name": "category_1"
                        }
                     },
                     {
                        "key": 3,
                        "value": {
                           "category_id": 3,
                           "description": "desc_3",
                           "name": "category_3"
                        }
                     }
                  ],
                  "list_comment": [
                     {
                        "comment_id": 1,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 3,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 5,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 7,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 2,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 4,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 6,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 8,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     }
                  ]
               },
               "comment_id": 1,
               "comment_text": "comment_1 text"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": "2019-04-01",
                     "name": "author name modified at index 1 => container is dirty !!!",
                     "sex": 1
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [
                     {
                        "key": 1,
                        "value": {
                           "category_id": 1,
                           "description": "desc_1",
                           "name": "category_1"
                        }
                     },
                     {
                        "key": 3,
                        "value": {
                           "category_id": 3,
                           "description": "desc_3",
                           "name": "category_3"
                        }
                     }
                  ],
                  "list_comment": [
                     {
                        "comment_id": 1,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 3,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 5,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 7,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 2,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 4,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 6,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 8,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     }
                  ]
               },
               "comment_id": 3,
               "comment_text": "comment_1 text"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": "2019-04-01",
                     "name": "author name modified at index 1 => container is dirty !!!",
                     "sex": 1
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [
                     {
                        "key": 1,
                        "value": {
                           "category_id": 1,
                           "description": "desc_1",
                           "name": "category_1"
                        }
                     },
                     {
                        "key": 3,
                        "value": {
                           "category_id": 3,
                           "description": "desc_3",
                           "name": "category_3"
                        }
                     }
                  ],
                  "list_comment": [
                     {
                        "comment_id": 1,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 3,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 5,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 7,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 2,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 4,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 6,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 8,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     }
                  ]
               },
               "comment_id": 5,
               "comment_text": "comment_1 text"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": "2019-04-01",
                     "name": "author name modified at index 1 => container is dirty !!!",
                     "sex": 1
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [
                     {
                        "key": 1,
                        "value": {
                           "category_id": 1,
                           "description": "desc_1",
                           "name": "category_1"
                        }
                     },
                     {
                        "key": 3,
                        "value": {
                           "category_id": 3,
                           "description": "desc_3",
                           "name": "category_3"
                        }
                     }
                  ],
                  "list_comment": [
                     {
                        "comment_id": 1,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 3,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 5,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 7,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 2,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 4,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 6,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 8,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     }
                  ]
               },
               "comment_id": 7,
               "comment_text": "comment_1 text"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": "2019-04-01",
                     "name": "author name modified at index 1 => container is dirty !!!",
                     "sex": 1
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [
                     {
                        "key": 1,
                        "value": {
                           "category_id": 1,
                           "description": "desc_1",
                           "name": "category_1"
                        }
                     },
                     {
                        "key": 3,
                        "value": {
                           "category_id": 3,
                           "description": "desc_3",
                           "name": "category_3"
                        }
                     }
                  ],
                  "list_comment": [
                     {
                        "comment_id": 1,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 3,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 5,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 7,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 2,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 4,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 6,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 8,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     }
                  ]
               },
               "comment_id": 2,
               "comment_text": "comment_2 text"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": "2019-04-01",
                     "name": "author name modified at index 1 => container is dirty !!!",
                     "sex": 1
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [
                     {
                        "key": 1,
                        "value": {
                           "category_id": 1,
                           "description": "desc_1",
                           "name": "category_1"
                        }
                     },
                     {
                        "key": 3,
                        "value": {
                           "category_id": 3,
                           "description": "desc_3",
                           "name": "category_3"
                        }
                     }
                  ],
                  "list_comment": [
                     {
                        "comment_id": 1,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 3,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 5,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 7,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 2,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 4,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 6,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 8,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     }
                  ]
               },
               "comment_id": 4,
               "comment_text": "comment_2 text"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": "2019-04-01",
                     "name": "author name modified at index 1 => container is dirty !!!",
                     "sex": 1
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [
                     {
                        "key": 1,
                        "value": {
                           "category_id": 1,
                           "description": "desc_1",
                           "name": "category_1"
                        }
                     },
                     {
                        "key": 3,
                        "value": {
                           "category_id": 3,
                           "description": "desc_3",
                           "name": "category_3"
                        }
                     }
                  ],
                  "list_comment": [
                     {
                        "comment_id": 1,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 3,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 5,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 7,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 2,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 4,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 6,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 8,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     }
                  ]
               },
               "comment_id": 6,
               "comment_text": "comment_2 text"
            },
            {
               "blog_id": {
                  "author_id": {
                     "author_id": "author_id_2",
                     "birthdate": "2019-04-01",
                     "name": "author name modified at index 1 => container is dirty !!!",
                     "sex": 1
                  },
                  "blog_id": 1,
                  "blog_text": "blog property 'text' modified => blog is dirty !!!",
                  "date_creation": "2019-04-01T16:18:54",
                  "list_category": [
                     {
                        "key": 1,
                        "value": {
                           "category_id": 1,
                           "description": "desc_1",
                           "name": "category_1"
                        }
                     },
                     {
                        "key": 3,
                        "value": {
                           "category_id": 3,
                           "description": "desc_3",
                           "name": "category_3"
                        }
                     }
                  ],
                  "list_comment": [
                     {
                        "comment_id": 1,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 3,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 5,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 7,
                        "comment_text": "comment_1 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 2,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 4,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 6,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     },
                     {
                        "comment_id": 8,
                        "comment_text": "comment_2 text",
                        "date_creation": "2019-04-01T16:18:54"
                     }
                  ]
               },
               "comment_id": 8,
               "comment_text": "comment_2 text"
            }
         ]
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": "2019-04-01",
            "name": "author name modified at index 1 => container is dirty !!!"
         },
         "blog_id": 2,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "list_comment": []
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": "2019-04-01",
            "name": "author name modified at index 1 => container is dirty !!!"
         },
         "blog_id": 3,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "list_comment": []
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": "2019-04-01",
            "name": "author name modified at index 1 => container is dirty !!!"
         },
         "blog_id": 4,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "list_comment": []
      }
   ],
   "request_id": "4c45fdf9-8001-4509-bb4b-ce27a4a8708a"
}


fetch_by_id

The fetch_by_id action gets an item from a table based on its unique identifier.

-- Example n°1 -- fetch a blog which has an unique identifier equals to 1 :

JSON request :
{
   "request_id": "4d6fbb9e-e088-482a-abfa-4e7ddee80569",
   "action": "fetch_by_id",
   "entity": "blog",
   "data": {
      "blog_id": 1
   }
}
JSON response :
{
   "data": {
      "author_id": {
         "author_id": "author_id_2",
         "birthdate": null,
         "list_blog": [],
         "name": "",
         "sex": 2
      },
      "blog_id": 1,
      "blog_text": "blog property 'text' modified => blog is dirty !!!",
      "date_creation": "2019-04-01T16:18:54",
      "list_category": [],
      "list_comment": []
   },
   "request_id": "4d6fbb9e-e088-482a-abfa-4e7ddee80569"
}


-- Example n°2 -- fetch only some blog's columns which has an unique identifier equals to 1 (other columns are part of JSON response but with an empty or null value) :

JSON request :
{
   "request_id": "72c9b362-d194-410e-98ed-23797a34318e",
   "action": "fetch_by_id",
   "entity": "blog",
   "data": {
      "blog_id": 1
   },
   "columns": [
      "blog_text",
      "date_creation"
   ]
}
JSON response :
{
   "data": {
      "author_id": null,
      "blog_id": 1,
      "blog_text": "blog property 'text' modified => blog is dirty !!!",
      "date_creation": "2019-04-01T16:18:54",
      "list_category": [],
      "list_comment": []
   },
   "request_id": "72c9b362-d194-410e-98ed-23797a34318e"
}


-- Example n°3 -- fetch a list of blogs based on their unique identifier :

JSON request :
{
   "request_id": "59c37f70-26ee-42e5-9177-b32c331adce1",
   "action": "fetch_by_id",
   "entity": "blog",
   "data": [
      {
         "blog_id": 1
      },
      {
         "blog_id": 2
      },
      {
         "blog_id": 3
      }
   ]
}
JSON response :
{
   "data": [
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": null,
            "list_blog": [],
            "name": "",
            "sex": 2
         },
         "blog_id": 1,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-04-01T16:18:54",
         "list_category": [],
         "list_comment": []
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": null,
            "list_blog": [],
            "name": "",
            "sex": 2
         },
         "blog_id": 2,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-04-01T16:18:54",
         "list_category": [],
         "list_comment": []
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": null,
            "list_blog": [],
            "name": "",
            "sex": 2
         },
         "blog_id": 3,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "date_creation": "2019-04-01T16:18:54",
         "list_category": [],
         "list_comment": []
      }
   ],
   "request_id": "59c37f70-26ee-42e5-9177-b32c331adce1"
}


-- Example n°4 -- fetch a list of blogs (with some relationships) based on their unique identifier, and define a JSON output format (all properties will not be exported to JSON response) :

JSON request :
{
   "request_id": "325d64f4-29ac-47ab-9846-d6a71a9e9d73",
   "action": "fetch_by_id",
   "entity": "blog",
   "data": [
      {
         "blog_id": 1
      },
      {
         "blog_id": 2
      }
   ],
   "relations": [
      "{ blog_text }",
      "author_id <author_alias> { name, birthdate }",
      "list_comment <list_comment_alias> { comment_text }"
   ],
   "output_format": [
      "{ blog_text }",
      "author_id { name, birthdate }",
      "list_comment { comment_text }"
   ]
}
JSON response :
{
   "data": [
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": "2019-04-01",
            "name": "author name modified at index 1 => container is dirty !!!"
         },
         "blog_id": 1,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "list_comment": [
            {
               "comment_id": 1,
               "comment_text": "comment_1 text"
            },
            {
               "comment_id": 2,
               "comment_text": "comment_2 text"
            },
            {
               "comment_id": 3,
               "comment_text": "comment_1 text"
            },
            {
               "comment_id": 4,
               "comment_text": "comment_2 text"
            },
            {
               "comment_id": 5,
               "comment_text": "comment_1 text"
            },
            {
               "comment_id": 6,
               "comment_text": "comment_2 text"
            },
            {
               "comment_id": 7,
               "comment_text": "comment_1 text"
            },
            {
               "comment_id": 8,
               "comment_text": "comment_2 text"
            }
         ]
      },
      {
         "author_id": {
            "author_id": "author_id_2",
            "birthdate": "2019-04-01",
            "name": "author name modified at index 1 => container is dirty !!!"
         },
         "blog_id": 2,
         "blog_text": "blog property 'text' modified => blog is dirty !!!",
         "list_comment": []
      }
   ],
   "request_id": "325d64f4-29ac-47ab-9846-d6a71a9e9d73"
}


fetch_by_query

The fetch_by_query action gets some items from a table filtered by a query.

-- Example n°1 -- fetch only items from author table with a sex of type female (female == enum equals to 1) :

JSON request :
{
   "request_id": "c178194c-a76f-4a77-af12-2b97fc7078e4",
   "action": "fetch_by_query",
   "entity": "author",
   "query": {
      "sql": "WHERE author.sex = :sex",
      "params": [
         {
            "key": ":sex",
            "value": 1
         }
      ]
   }
}
JSON response :
{
   "data": [
      {
         "author_id": "author_id_2",
         "birthdate": "2019-04-01",
         "list_blog": [],
         "name": "author name modified at index 1 => container is dirty !!!",
         "sex": 1
      },
      {
         "author_id": "author_id_3",
         "birthdate": "1998-03-06",
         "list_blog": [],
         "name": "author_3",
         "sex": 1
      }
   ],
   "request_id": "c178194c-a76f-4a77-af12-2b97fc7078e4"
}


-- Example n°2 -- fetch some items from author table (and all relationships) with a sex of type female :

JSON request :
{
   "request_id": "84e2e13a-0bf9-4d78-b655-970568a97e4c",
   "action": "fetch_by_query",
   "entity": "author",
   "query": {
      "sql": "WHERE author.sex = :sex",
      "params": [
         {
            "key": ":sex",
            "value": 1,
            "type": "in"
         }
      ]
   },
   "relations": [
      "*"
   ]
}
JSON response :
{
   "data": [
      {
         "author_id": "author_id_2",
         "birthdate": "2019-04-01",
         "list_blog": [
            {
               "author_id": {
                  "author_id": "author_id_2",
                  "birthdate": null,
                  "list_blog": [],
                  "name": "",
                  "sex": 2
               },
               "blog_id": 1,
               "blog_text": "blog property 'text' modified => blog is dirty !!!",
               "date_creation": "2019-04-01T16:18:54",
               "list_category": [],
               "list_comment": []
            },
            {
               "author_id": {
                  "author_id": "author_id_2",
                  "birthdate": null,
                  "list_blog": [],
                  "name": "",
                  "sex": 2
               },
               "blog_id": 2,
               "blog_text": "blog property 'text' modified => blog is dirty !!!",
               "date_creation": "2019-04-01T16:18:54",
               "list_category": [],
               "list_comment": []
            },
            {
               "author_id": {
                  "author_id": "author_id_2",
                  "birthdate": null,
                  "list_blog": [],
                  "name": "",
                  "sex": 2
               },
               "blog_id": 3,
               "blog_text": "blog property 'text' modified => blog is dirty !!!",
               "date_creation": "2019-04-01T16:18:54",
               "list_category": [],
               "list_comment": []
            },
            {
               "author_id": {
                  "author_id": "author_id_2",
                  "birthdate": null,
                  "list_blog": [],
                  "name": "",
                  "sex": 2
               },
               "blog_id": 4,
               "blog_text": "blog property 'text' modified => blog is dirty !!!",
               "date_creation": "2019-04-01T16:18:54",
               "list_category": [],
               "list_comment": []
            }
         ],
         "name": "author name modified at index 1 => container is dirty !!!",
         "sex": 1
      },
      {
         "author_id": "author_id_3",
         "birthdate": "1998-03-06",
         "list_blog": [],
         "name": "author_3",
         "sex": 1
      }
   ],
   "request_id": "84e2e13a-0bf9-4d78-b655-970568a97e4c"
}


-- Example n°3 -- fetch some items from author table (and all relationships) with a sex of type female, and define a JSON output format (all properties will not be exported to JSON response) :

JSON request :
{
   "request_id": "c18b59e7-54f9-4a4f-843d-f0797f4fb676",
   "action": "fetch_by_query",
   "entity": "author",
   "query": {
      "sql": "WHERE author.sex = :sex",
      "params": [
         {
            "key": ":sex",
            "value": 1,
            "type": "in"
         }
      ]
   },
   "relations": [
      "*"
   ],
   "output_format": [
      "{ birthdate, name }",
      "list_blog { blog_text, date_creation }"
   ]
}
JSON response :
{
   "data": [
      {
         "author_id": "author_id_2",
         "birthdate": "2019-04-01",
         "list_blog": [
            {
               "blog_id": 1,
               "blog_text": "blog property 'text' modified => blog is dirty !!!",
               "date_creation": "2019-04-01T16:18:54"
            },
            {
               "blog_id": 2,
               "blog_text": "blog property 'text' modified => blog is dirty !!!",
               "date_creation": "2019-04-01T16:18:54"
            },
            {
               "blog_id": 3,
               "blog_text": "blog property 'text' modified => blog is dirty !!!",
               "date_creation": "2019-04-01T16:18:54"
            },
            {
               "blog_id": 4,
               "blog_text": "blog property 'text' modified => blog is dirty !!!",
               "date_creation": "2019-04-01T16:18:54"
            }
         ],
         "name": "author name modified at index 1 => container is dirty !!!"
      },
      {
         "author_id": "author_id_3",
         "birthdate": "1998-03-06",
         "list_blog": [],
         "name": "author_3"
      }
   ],
   "request_id": "c18b59e7-54f9-4a4f-843d-f0797f4fb676"
}


count

The count action returns a number of items from a table in database with or without a query (and with or without relationships).

-- Example n°1 -- count all blogs stored in database :

JSON request :
{
   "request_id": "1ef62fd7-d847-4d67-9fd0-0207af463aa4",
   "action": "count",
   "entity": "blog"
}
JSON response :
{
   "data": {
      "count": 4
   },
   "request_id": "1ef62fd7-d847-4d67-9fd0-0207af463aa4"
}


-- Example n°2 -- count items from author table with a sex of type female :

JSON request :
{
   "request_id": "a80646d1-5a42-46fb-9306-3b91c7f594c8",
   "action": "count",
   "entity": "author",
   "query": {
      "sql": "WHERE author.sex = :sex",
      "params": [
         {
            "key": ":sex",
            "value": 1
         }
      ]
   }
}
JSON response :
{
   "data": {
      "count": 2
   },
   "request_id": "a80646d1-5a42-46fb-9306-3b91c7f594c8"
}


-- Example n°3 -- count all blogs associated to an author with a sex of type female :

JSON request :
{
   "request_id": "6ef252f7-385c-465e-8304-b9afa9fea490",
   "action": "count",
   "entity": "blog",
   "query": {
      "sql": "WHERE author_alias.sex = :sex",
      "params": [
         {
            "key": ":sex",
            "value": 1
         }
      ]
   },
   "relations": [
      "author_id <author_alias> { sex }"
   ]
}
JSON response :
{
   "data": {
      "count": 4
   },
   "request_id": "6ef252f7-385c-465e-8304-b9afa9fea490"
}


exist

The exist action checks if an item from a table exists in database based on its unique identifier.

-- Example n°1 -- check if a blog with unique identifier equals to 1 exists in database :

JSON request :
{
   "request_id": "e8db33db-b249-4349-93fe-ad12e208520e",
   "action": "exist",
   "entity": "blog",
   "data": {
      "blog_id": 1
   }
}
JSON response :
{
   "data": {
      "exist": true
   },
   "request_id": "e8db33db-b249-4349-93fe-ad12e208520e"
}


-- Example n°2 -- check if several blogs exist (based on their unique identifier) :

JSON request :
{
   "request_id": "f2d6ca3f-36de-4920-8f4c-c04842603467",
   "action": "exist",
   "entity": "blog",
   "data": [
      {
         "blog_id": 1
      },
      {
         "blog_id": 999
      },
      {
         "blog_id": 3
      }
   ]
}
JSON response :
{
   "data": {
      "exist": false
   },
   "request_id": "f2d6ca3f-36de-4920-8f4c-c04842603467"
}


-- Example n°3 -- check if an author exists :

JSON request :
{
   "request_id": "2c7df172-8010-4816-b8e1-3edbb0b0b90e",
   "action": "exist",
   "entity": "author",
   "data": {
      "author_id": "author_id_2"
   }
}
JSON response :
{
   "data": {
      "exist": true
   },
   "request_id": "2c7df172-8010-4816-b8e1-3edbb0b0b90e"
}


Insert

The insert action adds 1 or several items in database. Unique identifiers generated by database (for example auto-incremented identifiers) are provided in JSON response.

-- Example n°1 -- insert a blog in database :

JSON request :
{
   "request_id": "573e4940-607a-4037-8a09-11ec52deb21c",
   "action": "insert",
   "entity": "blog",
   "data": {
      "blog_text": "this is a new blog from QxOrm REST API !",
      "date_creation": "2018-01-30T12:42:01",
      "author_id": "author_id_2"
   }
}
JSON response :
{
   "data": {
      "blog_id": 5
   },
   "request_id": "573e4940-607a-4037-8a09-11ec52deb21c"
}


-- Example n°2 -- insert a list of blogs in database :

JSON request :
{
   "request_id": "6ade2d01-086c-45d6-971b-b65e8836475f",
   "action": "insert",
   "entity": "blog",
   "data": [
      {
         "blog_text": "new blog from QxOrm REST API !",
         "date_creation": "2018-01-30T12:42:01",
         "author_id": "author_id_2"
      },
      {
         "blog_text": "another blog from QxOrm REST API !",
         "date_creation": "2016-06-12T08:33:12",
         "author_id": "author_id_1"
      }
   ]
}
JSON response :
{
   "data": [
      {
         "blog_id": 6
      },
      {
         "blog_id": 7
      }
   ],
   "request_id": "6ade2d01-086c-45d6-971b-b65e8836475f"
}


-- Example n°3 -- insert an author in database :

JSON request :
{
   "request_id": "0cffa916-99f4-4395-bccd-02918a4b3c57",
   "action": "insert",
   "entity": "author",
   "data": {
      "author_id": "author_id_from_rest_api",
      "birthdate": "1978-05-11",
      "name": "new author created by QxOrm REST API",
      "sex": 1
   }
}
JSON response :
{
   "data": {
      "author_id": "author_id_from_rest_api"
   },
   "request_id": "0cffa916-99f4-4395-bccd-02918a4b3c57"
}

Note : the author table requires an unique identifier filled by caller (because it's not auto-incremented). If we execute the same JSON request a second time, then we have following error :
{
   "error": {
      "code": 19,
      "desc": "Unable to fetch row\ncolumn author_id is not unique"
   },
   "request_id": "0cffa916-99f4-4395-bccd-02918a4b3c57"
}


Update

The update action modifies 1 or several items in database.

-- Example n°1 -- update a blog with unique identifier equals to 1 :

JSON request :
{
   "request_id": "4fa24a7f-a3d8-4bbf-85c1-c86df83dec0b",
   "action": "update",
   "entity": "blog",
   "data": {
      "blog_id": 1,
      "blog_text": "modify blog from QxOrm REST API",
      "date_creation": "2013-11-25T09:56:33",
      "author_id": "author_id_1"
   }
}
JSON response :
{
   "data": {
      "blog_id": 1
   },
   "request_id": "4fa24a7f-a3d8-4bbf-85c1-c86df83dec0b"
}


-- Example n°2 -- update only some columns of a blog :

JSON request :
{
   "request_id": "d0704db1-5c3a-48ad-b27e-14aa54ac0efb",
   "action": "update",
   "entity": "blog",
   "data": {
      "blog_id": 2,
      "blog_text": "modify blog from QxOrm REST API",
      "date_creation": "2013-11-25T09:56:33"
   },
   "columns": [
      "blog_text",
      "date_creation"
   ]
}
JSON response :
{
   "data": {
      "blog_id": 2
   },
   "request_id": "d0704db1-5c3a-48ad-b27e-14aa54ac0efb"
}


-- Example n°3 -- update a list of author :

JSON request :
{
   "request_id": "26ec3a7b-cf2d-47f7-bab7-db303f15ee51",
   "action": "update",
   "entity": "author",
   "data": [
      {
         "author_id": "author_id_from_rest_api",
         "birthdate": "1992-11-03",
         "name": "modify author from QxOrm REST API",
         "sex": 0
      },
      {
         "author_id": "author_id_1",
         "birthdate": "1978-12-25",
         "name": "modify another author from QxOrm REST API",
         "sex": 2
      }
   ]
}
JSON response :
{
   "data": [
      {
         "author_id": "author_id_from_rest_api"
      },
      {
         "author_id": "author_id_1"
      }
   ],
   "request_id": "26ec3a7b-cf2d-47f7-bab7-db303f15ee51"
}


Save (insert or update)

The save action adds or modifies (insert or update) 1 or several items in database. When inserting, unique identifiers generated by database (for example auto-incremented identifiers) are provided in JSON response.

JSON request has an optional parameter named save_mode which can have following values :
  • check_insert_or_update : save the instance and its relationships recursively (several levels of relations) checking for each relation if an insert or update is required (can be slow with a lot of relations) ;
  • insert_only : insert recursively (several levels of relations) the instance and its relationships ;
  • update_only : update recursively (several levels of relations) the instance and its relationships.

-- Example n°1 -- save (insert or update based on the unique identifier) a blog in database :

JSON request :
{
   "request_id": "ec3c71eb-5014-4b36-85a0-aeb7ae48a5e9",
   "action": "save",
   "entity": "blog",
   "data": {
      "blog_id": 1,
      "blog_text": "modify blog from QxOrm REST API",
      "date_creation": "2013-11-25T09:56:33",
      "author_id": "author_id_1"
   }
}
JSON response :
{
   "data": {
      "blog_id": 1
   },
   "request_id": "ec3c71eb-5014-4b36-85a0-aeb7ae48a5e9"
}


-- Example n°2 -- save (insert or update based on the unique identifier) a list of blogs in database :

JSON request :
{
   "request_id": "dc7c804e-f95a-4a9b-a4e3-547adcacf090",
   "action": "save",
   "entity": "blog",
   "data": [
      {
         "blog_id": 1,
         "blog_text": "save blog from QxOrm REST API !",
         "date_creation": "2018-01-30T12:42:01",
         "author_id": "author_id_2"
      },
      {
         "blog_text": "save another blog from QxOrm REST API !",
         "date_creation": "2016-06-12T08:33:12",
         "author_id": "author_id_1"
      }
   ]
}
JSON response :
{
   "data": [
      {
         "blog_id": 1
      },
      {
         "blog_id": 5
      }
   ],
   "request_id": "dc7c804e-f95a-4a9b-a4e3-547adcacf090"
}


-- Example n°3 -- save (insert or update based on the unique identifier) a blog and all its relationships recursively (several levels) :

JSON request :
{
   "request_id": "5b78e468-2fa3-4aeb-82ce-4d85408f5fa7",
   "action": "save",
   "entity": "blog",
   "data": {
      "blog_id": 1,
      "blog_text": "save recursive blog from QxOrm REST API",
      "date_creation": "2013-11-25T09:56:33",
      "author_id": {
         "author_id": "author_id_1",
         "birthdate": "1965-07-21",
         "name": "save recursive author from QxOrm REST API",
         "sex": 0
      }
   },
   "save_mode": "check_insert_or_update"
}
JSON response :
{
   "data": {
      "blog_id": 1
   },
   "request_id": "5b78e468-2fa3-4aeb-82ce-4d85408f5fa7"
}


-- Example n°4 -- insert (save_mode = insert_only) a blog and all its relationships recursively (several levels) :

JSON request :
{
   "request_id": "ef147c62-74e0-4be2-a294-ffeb020d5304",
   "action": "save",
   "entity": "blog",
   "data": {
      "blog_text": "save recursive - new blog from QxOrm REST API",
      "date_creation": "2013-11-25T09:56:33",
      "author_id": {
         "author_id": "author_id_save_recursive",
         "birthdate": "1965-07-21",
         "name": "save recursive (insert only) author from QxOrm REST API",
         "sex": 0
      }
   },
   "save_mode": "insert_only"
}
JSON response :
{
   "data": {
      "blog_id": 7
   },
   "request_id": "ef147c62-74e0-4be2-a294-ffeb020d5304"
}


Delete

This chapter provides several ways to remove data from database (delete or destroy) :
Note : difference between delete and destroy is soft delete behaviour (logical delete).

delete_all / destroy_all

The delete_all and destroy_all actions remove all items (rows) from a table. The difference between delete and destroy is soft delete behaviour (logical delete).

-- Example n°1 -- delete all rows from the comment table :

JSON request :
{
   "request_id": "7b06b5c0-409f-4e0d-bfc4-acafbfe7e796",
   "action": "delete_all",
   "entity": "comment"
}
JSON response :
{
   "data": {
      "deleted": true
   },
   "request_id": "7b06b5c0-409f-4e0d-bfc4-acafbfe7e796"
}


delete_by_query / destroy_by_query

The delete_by_query and destroy_by_query actions remove some items (rows) from a table based on a query. The difference between delete and destroy is soft delete behaviour (logical delete).

-- Example n°1 -- delete all rows from the author table with a sex of type female (female = enum with value 1) :

JSON request :
{
   "request_id": "169ff0be-6e49-457b-a99c-22bd7141dc02",
   "action": "delete_by_query",
   "entity": "author",
   "query": {
      "sql": "WHERE author.sex = :sex",
      "params": [
         {
            "key": ":sex",
            "value": 1
         }
      ]
   }
}
JSON response :
{
   "data": {
      "deleted": true
   },
   "request_id": "169ff0be-6e49-457b-a99c-22bd7141dc02"
}


delete_by_id / destroy_by_id

The delete_by_id and destroy_by_id actions remove some items (rows) from a table based on the unique identifier. The difference between delete and destroy is soft delete behaviour (logical delete).

-- Example n°1 -- delete from database the blog with unique identifier equals to 4 :

JSON request :
{
   "request_id": "80bff383-8ebd-4bde-bb42-37b6f67bc39f",
   "action": "delete_by_id",
   "entity": "blog",
   "data": {
      "blog_id": 4
   }
}
JSON response :
{
   "data": {
      "blog_id": 4
   },
   "request_id": "80bff383-8ebd-4bde-bb42-37b6f67bc39f"
}


-- Example n°2 -- delete from database a list of 2 blogs with unique identifier equals to 3 and 2 :

JSON request :
{
   "request_id": "38020cb7-d725-4c0e-80a0-63db7569155e",
   "action": "delete_by_id",
   "entity": "blog",
   "data": [
      {
         "blog_id": 3
      },
      {
         "blog_id": 2
      }
   ]
}
JSON response :
{
   "data": [
      {
         "blog_id": 3
      },
      {
         "blog_id": 2
      }
   ],
   "request_id": "38020cb7-d725-4c0e-80a0-63db7569155e"
}


Validate

The validate action checks some properties of your instance (without triggering any action to database). The validate action calls the QxValidator module from QxOrm library.

-- Example n°1 -- a blog must contain some text (blog_text property) to be saved in database. With the following JSON request, we get a JSON response with an error message which means that the blog instance is not valid :

JSON request :
{
   "request_id": "92043c2b-4ba8-4583-8fad-c828251734ba",
   "action": "validate",
   "entity": "blog",
   "data": {
      "blog_id": 9999,
      "blog_text": ""
   }
}
JSON response :
{
   "data": {
      "invalid_values": [
         "blog",
         [
            {
               "message": "'blog_text' property cannot be empty",
               "path": "blog"
            }
         ]
      ]
   },
   "request_id": "92043c2b-4ba8-4583-8fad-c828251734ba"
}


-- Example n°2 -- we add a value to blog_text property, so the blog instance becomes valid (the JSON response has an invalid_values property with a null value) :

JSON request :
{
   "request_id": "92043c2b-4ba8-4583-8fad-c828251734ba",
   "action": "validate",
   "entity": "blog",
   "data": {
      "blog_id": 9999,
      "blog_text": "my blog text !!!"
   }
}
JSON response :
{
   "data": {
      "invalid_values": null
   },
   "request_id": "92043c2b-4ba8-4583-8fad-c828251734ba"
}


Custom SQL query or stored procedure

The call_custom_query action executes a custom SQL query or a stored procedure.

-- Example n°1 -- insert in database a new author with a custom SQL query :

JSON request :
{
   "request_id": "ff2a2256-041d-4c5f-bd86-3745ce46ead8",
   "action": "call_custom_query",
   "query": {
      "sql": "INSERT INTO author (author_id, name, birthdate, sex) VALUES (:author_id, :name, :birthdate, :sex)",
      "params": [
         {
            "key": ":author_id",
            "value": "author_id_custom_query"
         },
         {
            "key": ":name",
            "value": "new author inserted by custom query"
         },
         {
            "key": ":birthdate",
            "value": "20190215"
         },
         {
            "key": ":sex",
            "value": 2
         }
      ]
   }
}
JSON response :
{
   "data": {
      "query_output": {
         "distinct": false,
         "list_values": {
            ":author_id": [
               "author_id_custom_query",
               1
            ],
            ":birthdate": [
               "20190215",
               1
            ],
            ":name": [
               "new author inserted by custom query",
               1
            ],
            ":sex": [
               2,
               1
            ]
         },
         "parenthesis_count": 0,
         "query": [
            "INSERT INTO author (author_id, name, birthdate, sex) VALUES (:author_id, :name, :birthdate, :sex)"
         ],
         "response": "",
         "result_position_by_key": {},
         "result_values": [],
         "sql_element_index": 0,
         "sql_element_list": [],
         "sql_element_temp_type": 0,
         "type": ""
      }
   },
   "request_id": "ff2a2256-041d-4c5f-bd86-3745ce46ead8"
}


Call C++ natives functions

The call_entity_function action executes C++ natives functions registered into QxOrm context.
Prerequisites : the C++ native function must be a static method with this signature : static QJsonValue myNativeCppFct(const QJsonValue & request);

Here is an example of C++ function registered into QxOrm context, this function can be called by the JSON API engine (QxRestApi module) :

namespace qx {
template <> void register_class(QxClass<blog> & t)
{
   // Register 'helloWorld()' static function in QxOrm context (can be called by QxRestApi JSON API module)
   t.fctStatic_1<QJsonValue, const QJsonValue & >(& blog::helloWorld, "helloWorld");
}}

// 'helloWorld()' static function implementation
QJsonValue blog::helloWorld(const QJsonValue & request)
{
   QJsonObject response;
   response.insert("request", request);
   response.insert("response", QString("Hello World !"));
   return response;
}


Here is how to execute the C++ helloWorld function from JSON API using call_entity_function action :

JSON request :
{
   "request_id": "ab1ba7d3-9f98-4b18-a310-a9c34498d043",
   "action": "call_entity_function",
   "entity": "blog",
   "fct": "helloWorld",
   "data": {
      "param1": "test",
      "param2": "static fct call"
   }
}
JSON response :
{
   "data": {
      "request": {
         "param1": "test",
         "param2": "static fct call"
      },
      "response": "Hello World !"
   },
   "request_id": "ab1ba7d3-9f98-4b18-a310-a9c34498d043"
}


Meta-data (C++ classes registered into QxOrm context)

The get_meta_data action fetches some meta-data from 1 or all entities registered into QxOrm context (C++ classes structure with list of properties and relationships).

-- Example n°1 -- get all meta-data of qxBlogRestApi example project :

JSON request :
{
   "request_id": "842ed7b5-9b94-455f-86dc-32992866b3d5",
   "action": "get_meta_data",
   "entity": "*"
}
JSON response :
{
   "data": {
      "entities": [
         {
            "base_entity": "",
            "description": "",
            "entity_id": {
               "description": "",
               "key": "author_id",
               "type": "QString"
            },
            "key": "author",
            "name": "author",
            "properties": [
               {
                  "description": "",
                  "key": "name",
                  "type": "QString"
               },
               {
                  "description": "",
                  "key": "birthdate",
                  "type": "QDate"
               },
               {
                  "description": "",
                  "key": "sex",
                  "type": "enum author::enum_sex *"
               }
            ],
            "relations": [
               {
                  "description": "",
                  "key": "list_blog",
                  "target": "blog",
                  "type": "std::vector<std::shared_ptr<blog>>",
                  "type_relation": "relation one-to-many"
               }
            ],
            "version": 0
         },
         {
            "base_entity": "",
            "description": "",
            "entity_id": {
               "description": "",
               "key": "blog_id",
               "type": "long"
            },
            "key": "blog",
            "name": "blog",
            "properties": [
               {
                  "description": "",
                  "key": "blog_text",
                  "type": "QString"
               },
               {
                  "description": "",
                  "key": "date_creation",
                  "type": "QDateTime"
               }
            ],
            "relations": [
               {
                  "description": "",
                  "key": "author_id",
                  "target": "author",
                  "type": "std::shared_ptr<author>",
                  "type_relation": "relation many-to-one"
               },
               {
                  "description": "",
                  "key": "list_comment",
                  "target": "comment",
                  "type": "QList<std::shared_ptr<comment>>",
                  "type_relation": "relation one-to-many"
               },
               {
                  "description": "",
                  "key": "list_category",
                  "target": "category",
                  "type": "qx::QxCollection<long, QSharedPointer<category>>",
                  "type_relation": "relation many-to-many"
               }
            ],
            "version": 0
         },
         {
            "base_entity": "",
            "description": "",
            "entity_id": {
               "description": "",
               "key": "comment_id",
               "type": "long"
            },
            "key": "comment",
            "name": "comment",
            "properties": [
               {
                  "description": "",
                  "key": "comment_text",
                  "type": "QString"
               },
               {
                  "description": "",
                  "key": "date_creation",
                  "type": "QDateTime"
               }
            ],
            "relations": [
               {
                  "description": "",
                  "key": "blog_id",
                  "target": "blog",
                  "type": "std::shared_ptr<blog>",
                  "type_relation": "relation many-to-one"
               }
            ],
            "version": 0
         },
         {
            "base_entity": "",
            "description": "",
            "entity_id": {
               "description": "",
               "key": "category_id",
               "type": "long"
            },
            "key": "category",
            "name": "category",
            "properties": [
               {
                  "description": "",
                  "key": "name",
                  "type": "QString"
               },
               {
                  "description": "",
                  "key": "description",
                  "type": "QString"
               }
            ],
            "relations": [
               {
                  "description": "",
                  "key": "list_blog",
                  "target": "blog",
                  "type": "qx::QxCollection<long, std::shared_ptr<blog>>",
                  "type_relation": "relation many-to-many"
               }
            ],
            "version": 0
         }
      ]
   },
   "request_id": "842ed7b5-9b94-455f-86dc-32992866b3d5"
}


Send a list of JSON requests

To limit transactions count between client and server, it is possible to send a list of JSON requests to QxRestApi module. Each JSON request can contain its own unique identifier request_id (to match a JSON response with the right JSON request). When a list of JSON requests is sent to QxRestApi module, then a transaction (commit/rollback) is automatically created (so if an error occurred, then all actions in database are cancelled).

-- Example n°1 -- send a list of 4 JSON requests to QxRestApi module (1 request to fetch project meta-data + 3 requests to fetch_all blogs with several ways to get relationships) :

JSON request :
[
   {
      "request_id": "53c96a23-2566-4b3d-ae6c-bff634600e79",
      "action": "get_meta_data",
      "entity": "*"
   },
   {
      "request_id": "56e3ca99-5c12-4aca-aa6c-7d0e43c1e636",
      "action": "fetch_all",
      "entity": "blog"
   },
   {
      "request_id": "692968e4-8885-41ad-b918-6ce2791b3bb8",
      "action": "fetch_all",
      "entity": "blog",
      "data": [
         {
            "key": "",
            "value": ""
         }
      ]
   },
   {
      "request_id": "4ffe38a6-d642-44b0-8be1-198e84256321",
      "action": "fetch_all",
      "entity": "blog",
      "relations": [
         "*->*"
      ]
   }
]
JSON response :
[
   {
      "data": {
         "entities": [
            {
               "base_entity": "",
               "description": "",
               "entity_id": {
                  "description": "",
                  "key": "author_id",
                  "type": "QString"
               },
               "key": "author",
               "name": "author",
               "properties": [
                  {
                     "description": "",
                     "key": "name",
                     "type": "QString"
                  },
                  {
                     "description": "",
                     "key": "birthdate",
                     "type": "QDate"
                  },
                  {
                     "description": "",
                     "key": "sex",
                     "type": "enum author::enum_sex *"
                  }
               ],
               "relations": [
                  {
                     "description": "",
                     "key": "list_blog",
                     "target": "blog",
                     "type": "std::vector<std::shared_ptr<blog>>",
                     "type_relation": "relation one-to-many"
                  }
               ],
               "version": 0
            },
            {
               "base_entity": "",
               "description": "",
               "entity_id": {
                  "description": "",
                  "key": "blog_id",
                  "type": "long"
               },
               "key": "blog",
               "name": "blog",
               "properties": [
                  {
                     "description": "",
                     "key": "blog_text",
                     "type": "QString"
                  },
                  {
                     "description": "",
                     "key": "date_creation",
                     "type": "QDateTime"
                  }
               ],
               "relations": [
                  {
                     "description": "",
                     "key": "author_id",
                     "target": "author",
                     "type": "std::shared_ptr<author>",
                     "type_relation": "relation many-to-one"
                  },
                  {
                     "description": "",
                     "key": "list_comment",
                     "target": "comment",
                     "type": "QList<std::shared_ptr<comment>>",
                     "type_relation": "relation one-to-many"
                  },
                  {
                     "description": "",
                     "key": "list_category",
                     "target": "category",
                     "type": "qx::QxCollection<long, QSharedPointer<category>>",
                     "type_relation": "relation many-to-many"
                  }
               ],
               "version": 0
            },
            {
               "base_entity": "",
               "description": "",
               "entity_id": {
                  "description": "",
                  "key": "comment_id",
                  "type": "long"
               },
               "key": "comment",
               "name": "comment",
               "properties": [
                  {
                     "description": "",
                     "key": "comment_text",
                     "type": "QString"
                  },
                  {
                     "description": "",
                     "key": "date_creation",
                     "type": "QDateTime"
                  }
               ],
               "relations": [
                  {
                     "description": "",
                     "key": "blog_id",
                     "target": "blog",
                     "type": "std::shared_ptr<blog>",
                     "type_relation": "relation many-to-one"
                  }
               ],
               "version": 0
            },
            {
               "base_entity": "",
               "description": "",
               "entity_id": {
                  "description": "",
                  "key": "category_id",
                  "type": "long"
               },
               "key": "category",
               "name": "category",
               "properties": [
                  {
                     "description": "",
                     "key": "name",
                     "type": "QString"
                  },
                  {
                     "description": "",
                     "key": "description",
                     "type": "QString"
                  }
               ],
               "relations": [
                  {
                     "description": "",
                     "key": "list_blog",
                     "target": "blog",
                     "type": "qx::QxCollection<long, std::shared_ptr<blog>>",
                     "type_relation": "relation many-to-many"
                  }
               ],
               "version": 0
            }
         ]
      },
      "request_id": "53c96a23-2566-4b3d-ae6c-bff634600e79"
   },
   {
      "data": [
         {
            "author_id": {
               "author_id": "author_id_1",
               "birthdate": null,
               "list_blog": [],
               "name": "",
               "sex": 2
            },
            "blog_id": 1,
            "blog_text": "save recursive blog from QxOrm REST API",
            "date_creation": "2013-11-25T09:56:33",
            "list_category": [],
            "list_comment": []
         },
         {
            "author_id": {
               "author_id": "author_id_1",
               "birthdate": null,
               "list_blog": [],
               "name": "",
               "sex": 2
            },
            "blog_id": 5,
            "blog_text": "save another blog from QxOrm REST API !",
            "date_creation": "2016-06-12T08:33:12",
            "list_category": [],
            "list_comment": []
         },
         {
            "author_id": {
               "author_id": "author_id_save_recursive",
               "birthdate": null,
               "list_blog": [],
               "name": "",
               "sex": 2
            },
            "blog_id": 6,
            "blog_text": "save recursive - new blog from QxOrm REST API",
            "date_creation": "2013-11-25T09:56:33",
            "list_category": [],
            "list_comment": []
         },
         {
            "author_id": {
               "author_id": "author_id_save_recursive",
               "birthdate": null,
               "list_blog": [],
               "name": "",
               "sex": 2
            },
            "blog_id": 7,
            "blog_text": "save recursive - new blog from QxOrm REST API",
            "date_creation": "2013-11-25T09:56:33",
            "list_category": [],
            "list_comment": []
         }
      ],
      "request_id": "56e3ca99-5c12-4aca-aa6c-7d0e43c1e636"
   },
   {
      "data": [
         {
            "key": 1,
            "value": {
               "author_id": {
                  "author_id": "author_id_1",
                  "birthdate": null,
                  "list_blog": [],
                  "name": "",
                  "sex": 2
               },
               "blog_id": 1,
               "blog_text": "save recursive blog from QxOrm REST API",
               "date_creation": "2013-11-25T09:56:33",
               "list_category": [],
               "list_comment": []
            }
         },
         {
            "key": 5,
            "value": {
               "author_id": {
                  "author_id": "author_id_1",
                  "birthdate": null,
                  "list_blog": [],
                  "name": "",
                  "sex": 2
               },
               "blog_id": 5,
               "blog_text": "save another blog from QxOrm REST API !",
               "date_creation": "2016-06-12T08:33:12",
               "list_category": [],
               "list_comment": []
            }
         },
         {
            "key": 6,
            "value": {
               "author_id": {
                  "author_id": "author_id_save_recursive",
                  "birthdate": null,
                  "list_blog": [],
                  "name": "",
                  "sex": 2
               },
               "blog_id": 6,
               "blog_text": "save recursive - new blog from QxOrm REST API",
               "date_creation": "2013-11-25T09:56:33",
               "list_category": [],
               "list_comment": []
            }
         },
         {
            "key": 7,
            "value": {
               "author_id": {
                  "author_id": "author_id_save_recursive",
                  "birthdate": null,
                  "list_blog": [],
                  "name": "",
                  "sex": 2
               },
               "blog_id": 7,
               "blog_text": "save recursive - new blog from QxOrm REST API",
               "date_creation": "2013-11-25T09:56:33",
               "list_category": [],
               "list_comment": []
            }
         }
      ],
      "request_id": "692968e4-8885-41ad-b918-6ce2791b3bb8"
   },
   {
      "data": [
         {
            "author_id": {
               "author_id": "author_id_1",
               "birthdate": "2019-04-02",
               "list_blog": [
                  {
                     "author_id": {
                        "author_id": "author_id_1",
                        "birthdate": null,
                        "list_blog": [],
                        "name": "",
                        "sex": 2
                     },
                     "blog_id": 5,
                     "blog_text": "save another blog from QxOrm REST API !",
                     "date_creation": "2016-06-12T08:33:12",
                     "list_category": [],
                     "list_comment": []
                  },
                  {
                     "author_id": {
                        "author_id": "author_id_1",
                        "birthdate": null,
                        "list_blog": [],
                        "name": "",
                        "sex": 2
                     },
                     "blog_id": 1,
                     "blog_text": "save recursive blog from QxOrm REST API",
                     "date_creation": "2013-11-25T09:56:33",
                     "list_category": [],
                     "list_comment": []
                  }
               ],
               "name": "author_1",
               "sex": 0
            },
            "blog_id": 1,
            "blog_text": "save recursive blog from QxOrm REST API",
            "date_creation": "2013-11-25T09:56:33",
            "list_category": [],
            "list_comment": []
         },
         {
            "author_id": {
               "author_id": "author_id_1",
               "birthdate": "2019-04-02",
               "list_blog": [
                  {
                     "author_id": {
                        "author_id": "author_id_1",
                        "birthdate": null,
                        "list_blog": [],
                        "name": "",
                        "sex": 2
                     },
                     "blog_id": 5,
                     "blog_text": "save another blog from QxOrm REST API !",
                     "date_creation": "2016-06-12T08:33:12",
                     "list_category": [],
                     "list_comment": []
                  },
                  {
                     "author_id": {
                        "author_id": "author_id_1",
                        "birthdate": null,
                        "list_blog": [],
                        "name": "",
                        "sex": 2
                     },
                     "blog_id": 1,
                     "blog_text": "save recursive blog from QxOrm REST API",
                     "date_creation": "2013-11-25T09:56:33",
                     "list_category": [],
                     "list_comment": []
                  }
               ],
               "name": "author_1",
               "sex": 0
            },
            "blog_id": 5,
            "blog_text": "save another blog from QxOrm REST API !",
            "date_creation": "2016-06-12T08:33:12",
            "list_category": [],
            "list_comment": []
         },
         {
            "author_id": {
               "author_id": "author_id_save_recursive",
               "birthdate": null,
               "list_blog": [],
               "name": "",
               "sex": 2
            },
            "blog_id": 6,
            "blog_text": "save recursive - new blog from QxOrm REST API",
            "date_creation": "2013-11-25T09:56:33",
            "list_category": [],
            "list_comment": []
         },
         {
            "author_id": {
               "author_id": "author_id_save_recursive",
               "birthdate": null,
               "list_blog": [],
               "name": "",
               "sex": 2
            },
            "blog_id": 7,
            "blog_text": "save recursive - new blog from QxOrm REST API",
            "date_creation": "2013-11-25T09:56:33",
            "list_category": [],
            "list_comment": []
         }
      ],
      "request_id": "4ffe38a6-d642-44b0-8be1-198e84256321"
   }
]




QxOrm © 2011-2024 Lionel Marty - contact@qxorm.com