QxOrm Windows Linux Macintosh C++

Accueil T�l�chargement Exemple rapide Tutoriel (4)
Manuel (2)
Forum Nos clients

QxOrm >> Manuel d'utilisation de la biblioth�que QxOrm
Version courante :  QxOrm 1.5.0 - documentation en ligne de la biblioth�que QxOrm - GitHub
QxEntityEditor 1.2.8
Version fran�aise du site Web site english version
S�lection du manuel : Manuel QxOrm Manuel QxEntityEditor


Manuel d'utilisation de la biblioth�que QxOrm - Table des mati�res

  1. Introduction
    1. Biblioth�que QxOrm
    2. Aper�u rapide de l'application QxEntityEditor
    3. Convention d'�criture C++ utilis�e par la biblioth�que QxOrm
  2. Installation
    1. D�pendance � Qt
    2. D�pendance � boost (optionnel)
    3. Fichier de configuration QxOrm.pri (ou QxOrm.cmake)
    4. Compiler la biblioth�que QxOrm (avec qmake ou CMake)
    5. Pilotes SQL fournis par Qt (drivers)
  3. Persistance - Object Relational Mapping (ORM)
    1. D�finir une classe dans le contexte QxOrm (mapping)
      1. Cl� primaire autre que le type par d�faut "long"
      2. Cl� primaire sur plusieurs colonnes (composite key)
      3. Donn�es membres public/protected/private
      4. Espace de nom (namespace)
      5. Types C++ support�s par QxOrm
      6. D�finir une donn�e membre transient
    2. Connexion � la base de donn�es
    3. Sauvegarder une instance C++ en base de donn�es (insert/update)
    4. Supprimer une instance C++ de la base de donn�es (delete)
      1. Suppression logique (soft delete)
    5. R�cup�rer une instance C++ de la base de donn�es (fetch)
    6. Requ�tes SQL
      1. Utilisation de la classe qx::QxSqlQuery (ou son alias qx_query)
      2. Appel de proc�dure stock�e ou requ�te SQL personnalis�e
    7. Transactions (commit, rollback, session)
    8. Moteur de relations
      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. Requ�te SQL avec relations
      6. S�lectionner les colonnes des relations � r�cup�rer et d�finition des alias SQL
      7. Ajout SQL dans les clauses LEFT OUTER JOIN / INNER JOIN
    9. Collections support�es par QxOrm
      1. Collections de Qt
      2. Collections de boost
      3. Collections fournies par l'espace de nom standard std
      4. qx::QxCollection
    10. Pointeurs intelligents support�s par QxOrm (smart-pointers)
      1. Pointeurs intelligents de Qt
      2. Pointeurs intelligents de boost
      3. Pointeurs intelligents fournis par l'espace de nom standard std
      4. qx::dao::ptr
    11. D�clencheurs (triggers)
    12. Validation d'une instance C++ (validators)
    13. G�rer la valeur NULL de la base de donn�es
      1. boost::optional
      2. QVariant
    14. H�ritage et polymorphisme
    15. Interface qx::IxPersistable (classe abstraite)
    16. Utiliser le pattern C++ PIMPL (Private Implementation idiom ou d-pointer)
    17. Persister des types personnalis�s
    18. G�n�rer le sch�ma DDL SQL de la base de donn�es
    19. Associer un type SQL � une classe C++
    20. Effectuer des requ�tes asynchrones � la base de donn�es
    21. Gestion du cache pour sauvegarder des instances C++ (module QxCache)
    22. Travailler avec plusieurs bases de donn�es
    23. D�clarer une classe abstraite dans le contexte QxOrm
    24. D�clarer automatiquement les m�ta-propri�t�s de Qt (macro Q_PROPERTY)
  4. S�rialisation
    1. N� version pour assurer une compatibilit� ascendante
    2. Moteur QDataStream de Qt
    3. Moteur JSON de Qt
    4. Moteur XML de boost::serialization
    5. Moteur binaire de boost::serialization
    6. Autres types de s�rialisation propos�s par boost
    7. Cloner une instance C++
    8. Afficher le d�tail d'une instance C++ (dump au format XML ou JSON)
  5. Introspection - R�flexion
    1. Obtenir dynamiquement la valeur d'une donn�e membre
    2. Valoriser dynamiquement une donn�e membre
    3. Appeler dynamiquement une fonction
    4. Cr�er une instance C++ dynamiquement
    5. Parcourir la liste des classes/propri�t�s enregistr�es dans le contexte QxOrm
  6. Services : transf�rer la couche de donn�es persistante sur le r�seau (module QxService)
    1. Param�tres d'entr�e/sortie d'un service (requ�te/r�ponse)
    2. D�finir les fonctions publi�es par un service
    3. Liste des options disponibles c�t� serveur
    4. Param�trage de la connexion c�t� client
    5. Gestion de l'authentification dans un service
    6. Requ�tes client/serveur asynchrones
  7. Moteur mod�le/vue (module QxModelView)
    1. D�finir un mod�le "simple" (sans relation)
    2. Mod�les avec relations (notion de mod�les imbriqu�s)
    3. Int�raction avec les vues QML
    4. Int�raction avec les vues QtWidget
    5. Connexion d'un mod�le au module QxService
  8. QxOrm et MongoDB (C++ ODM Object Document Mapper)
    1. Pr�-requis : driver libmongoc et libbson
    2. Param�trage du fichier QxOrm.pri (ou QxOrm.cmake)
    3. Connexion � la base de donn�es MongoDB
    4. D�finition d'une classe persistante MongoDB (Collection) dans le contexte QxOrm (mapping)
      1. Gestion des cl�s primaires ObjectId
    5. Ins�rer une instance C++ (Document) dans la base de donn�es MongoDB (INSERT)
      1. Ins�rer une liste d'instances C++ (plusieurs Documents) dans la base de donn�es MongoDB (INSERT)
    6. Mettre � jour une instance C++ (Document) dans la base de donn�es MongoDB (UPDATE)
      1. Mettre � jour une liste d'instances C++ (plusieurs Documents) dans la base de donn�es MongoDB (UPDATE)
    7. Supprimer une instance C++ (Document) de la base de donn�es MongoDB (DELETE)
      1. Supprimer une liste d'instances C++ (plusieurs Documents) de la base de donn�es MongoDB (DELETE)
    8. R�cup�rer une instance C++ (Document) de la base de donn�es MongoDB (FETCH)
      1. R�cup�rer une liste d'instances C++ (plusieurs Documents) de la base de donn�es MongoDB (FETCH)
    9. Requ�tes JSON
      1. Utilisation de la classe qx::QxSqlQuery (ou son alias qx_query)
      2. Utiliser le moteur d'aggregation MongoDB
      3. Ajouter des propri�t�s � la requ�te de type : 'sort', 'limit', 'skip', etc...
      4. Ex�cuter une requ�te personnalis�e
    10. Moteur de relations (n�cessite une version MongoDB 3.6 ou +)
      1. Relations : Embedded vs Referenced
    11. Cr�ation automatique des index
  9. Serveur web HTTP/HTTPS (module QxHttpServer)
    1. Hello World !
    2. Param�trage du serveur web HTTP
      1. Connexions s�curis�es SSL/TLS
    3. Routage des URL (d�finir les endpoints / dispatcher)
      1. Routage dynamique des URL
    4. R�cup�rer les param�tres de la requ�te HTTP
    5. G�n�rer la r�ponse HTTP
    6. Sessions (stockage par client c�t� serveur)
    7. Cookies
    8. Gestion des fichiers statiques
    9. Encodage de transfert en bloc (chunked responses)
    10. Requ�tes par les API JSON (module QxRestApi)
    11. WebSocket
    12. Performance (test� avec Apache Benchmark)
      1. Am�liorer les performances avec epoll dispatcher sous Linux
  10. API REST JSON (module QxRestApi)
    1. Principe de fonctionnement
      1. Cas d'utilisation
    2. Projet de test qxBlogRestApi (QML et serveur web HTTP)
    3. R�cup�ration de donn�es (fetch/count/exist)
      1. fetch_all
      2. fetch_by_id
      3. fetch_by_query
      4. count
      5. exist
    4. Ajout de donn�es (insert)
    5. Mise � jour de donn�es (update)
    6. Sauvegarde de donn�es (save)
    7. Suppression de donn�es (delete)
      1. delete_all / destroy_all
      2. delete_by_query / destroy_by_query
      3. delete_by_id / destroy_by_id
    8. Validation de donn�es (validate)
    9. Appel RAW SQL ou proc�dure stock�e
    10. Appel fonctions natives C++
    11. Meta-data (structure des classes C++ enregistr�es dans le contexte QxOrm)
    12. Envoyer une liste de requ�tes JSON
qt_ambassador
QxOrm library has been accepted into the Qt Ambassador Program


Introduction

L'objectif de ce manuel utilisateur est de pr�senter de mani�re structur�e l'ensemble des fonctionnalit�s propos�es par la biblioth�que QxOrm. Ce manuel est destin� aux d�veloppeurs et architectes logiciel qui souhaitent g�rer une couche de donn�es persistante en C++/Qt. Des comp�tences techniques en C++ et base de donn�es sont requises pour la bonne compr�hension de ce document.

Remarque : la plupart des fonctionnalit�s pr�sent�es dans ce manuel peuvent �tre d�finies rapidement et facilement avec l'application QxEntityEditor (l'�diteur graphique de la biblioth�que QxOrm). Une documentation d�di�e � l'application QxEntityEditor est �galement disponible.

Autre remarque : ce manuel est bas� en grande partie sur l'ancienne FAQ du site QxOrm, toujours accessible en cliquant ici.

Biblioth�que QxOrm

QxOrm est une biblioth�que C++ open source de gestion de donn�es (Object Relational Mapping, ORM).
QxOrm est d�velopp� XDL Teamarty, Ing�nieur en d�veloppement logiciel depuis 2003.

� partir d'une simple fonction de param�trage (que l'on peut comparer avec un fichier de mapping XML Hibernate), vous aurez acc�s aux fonctionnalit�s suivantes :
  • Persistance : support des bases de donn�es SQLite, MySQL, PostgreSQL, Oracle, MS SQL Server, MongoDB (gestion des relations 1-1, 1-n, n-1 et n-n) ;
  • S�rialisation des donn�es (flux JSON, binaire et XML) ;
  • R�flexion (ou introspection ) pour acc�der dynamiquement aux classes, attributs et invoquer des m�thodes ;
  • Serveur web HTTP : serveur web compatible HTTP 1.1 autonome, performant, multi-plateforme et simple d'utilisation ;
  • API JSON : interop�rabilit� avec d'autres technologies que C++/Qt (web services REST, applications QML, langages de script).
QxOrm est d�pendant des excellentes biblioth�ques Qt (compatible � partir de la version 4.5.0) et boost (compatible � partir de la version 1.38, par d�faut seuls les fichiers d'en-t�te *.hpp sont n�cessaires).
La biblioth�que QxOrm a �t� retenue pour faire partie du programme Qt Ambassador.

Si vous trouvez un bug ou si vous avez une question concernant le fonctionnement de la biblioth�que QxOrm, vous pouvez envoyer un mail � : support@qxorm.com.
Un forum (en anglais) d�di� � QxOrm est disponible en cliquant ici.
Vous pouvez �galement retrouver la communaut� fran�aise de QxOrm sur le forum de Developpez.com.

Aper�u rapide de l'application QxEntityEditor

QxEntityEditor est un �diteur graphique pour la biblioth�que QxOrm : QxEntityEditor permet de g�rer graphiquement le mod�le d'entit�s.
QxEntityEditor est multi-plateforme (disponible pour Windows, Linux et Mac OS X) et g�n�re du code natif pour tous les environnements : bureau (Windows, Linux, Mac OS X), embarqu� et mobile (Android, iOS, Windows Phone, Raspberry Pi, etc.).
Une vid�o de pr�sentation de l'application QxEntityEditor est disponible.

QxEntityEditor est bas� sur un syst�me de plugins et propose diverses fonctionnalit�s pour importer/exporter le mod�le de donn�es :
  • g�n�ration automatique du code C++ (classes persistantes enregistr�es dans le contexte QxOrm) ;
  • g�n�ration automatique des scripts SQL DDL (sch�ma de base de donn�es) pour les bases SQLite, MySQL, PostgreSQL, Oracle et MS SQL Server ;
  • supporte l'�volution du sch�ma de base de donn�es pour chaque version d'un projet (ALTER TABLE, ADD COLUMN, DROP INDEX, etc.) ;
  • g�n�ration automatique des classes C++ de services pour transf�rer le mod�le de donn�es sur le r�seau, en utilisant le module QxService, pour cr�er rapidement des applications client/serveur ;
  • importation automatique des structures de bases de donn�es existantes (par connexion ODBC) pour les bases SQLite, MySQL, PostgreSQL, Oracle et MS SQL Server ;
  • parce que chaque projet est diff�rent, QxEntityEditor propose plusieurs outils pour personnaliser les fichiers g�n�r�s (notamment un moteur javascript et un d�bogueur int�gr�).
QxEntityEditor

QxEntityEditor est d�velopp� XDL Teamarty, Ing�nieur en d�veloppement logiciel depuis 2003.
Un manuel utilisateur d�di� � l'application QxEntityEditor est disponible.


Convention d'�criture C++ utilis�e par la biblioth�que QxOrm

La biblioth�que QxOrm utilise les conventions d'�criture de code C++ suivantes :
  • toutes les classes, fonctions, variables, etc... sont d�finies dans l'espace de nom (namespace) qx ;
  • les macro de QxOrm sont �crites sous la forme QX_... ;
  • les classes abstraites (ou interfaces) ont le pr�fixe Ix (par exemple : IxFactory est une interface pour la cr�ation d'instances) ;
  • les autres classes ont le pr�fixe Qx (par exemple : QxDataMember) ;
  • les collections d'objets ont pour suffixe X (par exemple : QxDataMemberX est une collection de QxDataMember) ;
  • les fonctions pour communiquer avec les bases de donn�es se trouvent sous le namespace qx::dao (par exemple : qx::dao::fetch_by_id()) ;
  • les fonctions pour la serialization des donn�es se trouvent sous le namespace qx::serialization (par exemple : qx::serialization::xml::to_file()) ;
  • le moteur de reflection (ou introspection) est accessible depuis la classe qx::QxClassX (par exemple qx::QxClassX::invoke() pour invoquer une m�thode de classe) ;
  • les classes de traits se trouvent sous le namespace qx::trait (par exemple : qx::trait::is_smart_ptr<T>).

Installation

La biblioth�que QxOrm est multi-plateforme et peut �tre install�e sur tous types d'environnement : Windows, Linux (Unix), Mac OS X, Android, iOS, Windows Phone, etc...
Un tutoriel complet (avec captures d'�cran) pour installer un environnement de d�veloppement avec QxOrm sous Windows est disponible en cliquant ici.

L'objectif de ce chapitre est de pr�senter rapidement les diff�rentes �tapes � suivre pour installer QxOrm sur tous types d'environnement :

D�pendance � Qt

Qt Qt : biblioth�que compl�te : IHM (QtGui), r�seau (QtNetwork), XML (QtXml), base de donn�es (QtSql), etc.
La documentation est excellente et le code C++ �crit � partir de cette biblioth�que est � la fois performant et simple de compr�hension.
Depuis le rachat par Nokia puis Digia et sa nouvelle licence LGPL, Qt est sans contexte la biblioth�que phare du moment.
QxOrm est compatible avec les principaux objets d�finis par Qt : QObject, QString, QDate, QTime, QDateTime, QList, QHash, QSharedPointer, QScopedPointer, etc.
Il est conseill� d'installer et d'utiliser la derni�re version de Qt disponible � l'adresse suivante : http://www.qt.io/

Remarque : par d�faut, la biblioth�que QxOrm d�pend uniquement des modules QtCore et QtSql. Il est possible d'activer des fonctionnalit�s suppl�mentaires gr�ce au fichier de configuration QxOrm.pri (ou QxOrm.cmake) : ces nouvelles fonctionnalit�s peuvent alors ajouter des d�pendances � QxOrm.

D�pendance � boost (optionnel)

Par d�faut, la biblioth�que QxOrm d�pend uniquement de Qt (QtCore et QtSql). L'installation de boost est optionnelle et non requise avec la configuration par d�faut.
Remarque : QxOrm propose 2 niveaux de d�pendance � boost en option :
  • une d�pendance uniquement aux fichiers d'en-t�tes de boost (*.hpp) : option de compilation _QX_ENABLE_BOOST ;
  • une d�pendance au module boost serialization : option de compilation _QX_ENABLE_BOOST_SERIALIZATION.

boost boost : de nombreux modules de la biblioth�que boost font partie de la nouvelle norme C++.
C'est une biblioth�que reconnue pour sa qualit�, son code 'C++ moderne', sa documentation, sa portabilit�, etc...
QxOrm utilise les fonctionnalit�s suivantes de boost : smart_pointer, type_traits, multi_index_container, unordered_container, any, tuple, foreach, function. Toutes ces fonctionnalit�s sont header only, la d�pendance au module serialization est optionnelle.
Il est conseill� d'installer et d'utiliser la derni�re version de boost disponible � l'adresse suivante : http://www.boost.org/

Remarque importante : avec l'option de compilation _QX_ENABLE_BOOST, la biblioth�que QxOrm d�pend uniquement des fichiers d'en-t�te *.hpp de boost (utilisation des biblioth�ques header only uniquement). L'installation de boost est donc tr�s simple puisqu'il suffit de d�zipper le package boost (pour disposer des fichiers d'en-t�te *.hpp).

Fichier de configuration QxOrm.pri (ou QxOrm.cmake)

Le fichier de configuration QxOrm.pri (ou QxOrm.cmake) est divis� en plusieurs sections (chacune �tant comment�e) et regroupe les diff�rents param�trages et options de compilation disponibles. Il est fortement recommand� de lire attentivement le fichier de configuration QxOrm.pri avant de compiler la biblioth�que QxOrm. Il est possible de conserver le param�trage par d�faut, seule la variable QX_BOOST_INCLUDE_PATH est n�cessaire si votre projet utilise le framework boost : cette variable indique o� trouver les fichiers d'en-t�te *.hpp de la biblioth�que boost :

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

Si vous ne souhaitez pas modifier le fichier de configuration QxOrm.pri, il est possible de d�finir une variable d'environnement nomm�e BOOST_INCLUDE : cette variable d'environnement sera alors utilis�e automatiquement pour valoriser QX_BOOST_INCLUDE_PATH (lire le fichier QxOrm.pri pour plus d'informations).

Voici une liste non exhaustive des diff�rentes options de compilation disponibles (lire le fichier de configuration QxOrm.pri pour plus de d�tails), aucune n'�tant activ�e par d�faut :
  • _QX_ENABLE_BOOST : ajoute une d�pendance aux fichiers d'en-t�tes de boost (*.hpp), support des classes boost::shared_ptr, boost::optional, boost::container, etc... ;
  • _QX_ENABLE_BOOST_SERIALIZATION : active les fonctionnalit�s de s�rialisation avec le module boost::serialization. Cette option n�cessite la compilation du binaire boost::serialization et ajoute donc une d�pendance � QxOrm ;

  • _QX_ENABLE_QT_GUI : support de la s�rialisation des types du module QtGui : QBrush, QColor, QFont, QImage, QMatrix, QPicture, QPixmap, QRegion. Cette option ajoute une d�pendance � QxOrm (QtGui) ;
  • _QX_ENABLE_QT_NETWORK : active le module QxService pour transf�rer la couche de donn�es persistante sur le r�seau (application client/serveur). Cette option ajoute une d�pendance � QxOrm (QtNetwork) ;
  • _QX_NO_PRECOMPILED_HEADER : d�sactive l'utilisation d'un en-t�te pr�compil� (permet de r�duire les temps de compilation d'un projet) : cette option est n�cessaire pour contourner un bug des versions r�centes de MinGW, pour tous les autres compilateurs il est recommand� de travailler avec un precompiled header ;
  • _QX_NO_RTTI : permet de compiler QxOrm et les projets d�pendants sans les informations de type C++ RTTI ;
  • _QX_STATIC_BUILD : permet de compiler la biblioth�que QxOrm en mode statique ;
  • _QX_UNITY_BUILD : r�duit les temps de compilation de la biblioth�que QxOrm en utilisant le concept unity build : un seul fichier source all.cpp � compiler. Il est recommand� d'activer cette option avec CMake (car ne supporte pas nativement les en-t�tes pr�compil�s) ;
  • _QX_ENABLE_MONGODB : support de la base de donn�es MongoDB, la biblioth�que QxOrm devient ainsi un ODM (Object Document Mapper).

Remarque : le fichier de configuration QxOrm.pri (ou QxOrm.cmake) devra �tre inclus dans tous les projets d�pendants de la biblioth�que QxOrm en ajoutant la ligne suivante dans le fichier *.pro du projet :

   include(my_path_to_QxOrm_library/QxOrm.pri)   

Autre remarque : � la place de qmake, il est possible d'utiliser l'outil de compilation CMake pour configurer et construire la biblioth�que QxOrm. CMake propose un outil graphique afin de visualiser et param�trer les diff�rentes options disponibles :

QxOrm and CMake

Compiler la biblioth�que QxOrm (avec qmake ou CMake)

QxOrm utilise le processus qmake de la biblioth�que Qt pour g�n�rer les makefile et compiler le projet (il est �galement possible d'utiliser l'outil de compilation CMake, un fichier CMakeLists.txt �tant fourni avec la biblioth�que QxOrm).
qmake est multi-plateforme et fonctionne parfaitement sous Windows, Linux (Unix) et Mac OS X.
Pour compiler QxOrm, il suffit d'ex�cuter les commandes suivantes :

   qmake
   make debug
   make release   

Sous Windows, des fichiers *.vcproj et *.sln sont disponibles pour les �diteurs Microsoft Visual C++.
Les fichiers *.pro sont lisibles par l'�diteur Qt Creator, et des plugins existent permettant de s'interfacer avec de nombreux �diteurs C++.
Les fichiers mingw_build_all_debug.bat et mingw_build_all_release.bat pr�sents dans le dossier ./tools/ permettent de compiler rapidement QxOrm ainsi que tous les tests avec le compilateur MinGW sous Windows.
Les fichiers gcc_build_all_debug.sh et gcc_build_all_release.sh pr�sents dans le dossier ./tools/ permettent de compiler rapidement QxOrm ainsi que tous les tests avec GCC sous Linux.
Enfin, les fichiers osx_build_all_debug.sh et osx_build_all_release.sh pr�sents dans le dossier ./tools/ permettent de compiler rapidement QxOrm ainsi que tous les tests sous Mac OS X (merci � Dominique Billet pour l'�criture des scripts).

Pilotes SQL fournis par Qt (drivers)

QxOrm utilise le moteur QtSql de Qt bas� sur un syst�me de plugin.
Une liste d�taill�e des bases de donn�es support�es est disponible sur le site de Qt.
Le plugin ODBC (QODBC) assure une compatibilit� avec de nombreuses bases de donn�es.
Pour des performances optimales, il est conseill� d'utiliser un plugin sp�cifique � une base de donn�es :
  • 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.
Remarque : pour se connecter � une base de donn�es Microsoft SQL Server, il est n�cessaire d'utiliser le pilote ODBC (plugin QODBC).

Autre remarque : la biblioth�que QxOrm supporte �galement la base de donn�es MongoDB (C++ ODM Object Document Mapper).

Persistance - Object Relational Mapping (ORM)

La biblioth�que QxOrm fournit un moteur de persistance des donn�es bas� sur le module QtSql de Qt. Ce moteur de persistance utilise la technique de programmation : Object Relational Mapping (ORM).

D�finition du site Wikipedia : un mapping objet-relationnel (en anglais object-relational mapping ou ORM) est une technique de programmation informatique qui cr�e l'illusion d'une base de donn�es orient�e objet � partir d'une base de donn�es relationnelle en d�finissant des correspondances entre cette base de donn�es et les objets du langage utilis�. On pourrait le d�signer par � correspondance entre monde objet et monde relationnel �. Le mapping objet-relationnel consiste � associer une ou plusieurs classes avec une table, et chaque attribut de la classe avec un champ de la table. Les frameworks de mapping objet-relationnel permettent d'�liminer la duplication de code dans les op�rations CRUD.

Pour effectuer cette correspondance entre le monde objet et le monde relationnel, ainsi pour que proposer l'ensemble de ses fonctionnalit�s, la biblioth�que QxOrm impose l'enregistrement de classes C++ dans le contexte QxOrm. Nous allons donc d�buter ce chapitre de la fa�on suivante : comment enregistrer une classe C++ dans le contexte QxOrm ?

Remarque : la biblioth�que QxOrm supporte �galement la base de donn�es MongoDB (C++ ODM Object Document Mapper).

D�finir une classe dans le contexte QxOrm (mapping)

Toutes les classes C++ peuvent �tre enregistr�es dans le contexte QxOrm : il n'y a pas besoin de d�river d'un super objet, et vous pouvez �crire vos m�thodes de classes et accesseurs sans aucune contrainte. Enregistrer une classe C++ dans le contexte QxOrm signifie :
  • dans le fichier en-t�te *.h contenant la d�finition de la classe : utilisation de la macro QX_REGISTER_HPP(class_name, base_class, class_version) ;
  • dans le fichier source *.cpp contenant l'impl�mentation de la classe : utilisation de la macro QX_REGISTER_CPP(class_name) ;
  • dans le fichier source *.cpp contenant l'impl�mentation de la classe : sp�cialisation de la fonction template : void qx::register_class<T>(qx::QxClass<T> & t).
Par exemple, voici comment d�clarer une classe person avec 4 propri�t�s enregistr�es dans le contexte QxOrm : id, firstName, lastName, birthDate :

* Fichier person.h :
#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_

* Fichier person.cpp :
#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
}}


Remarque : les m�thodes qx::QxClass<T>::id() et qx::QxClass<T>::data() retournent une instance de type : qx::IxDataMember (classe de base pour l'enregistrement des donn�es membre). Gr�ce � cette instance, il est possible de personnaliser le comportement par d�faut propos� par la classe qx::IxDataMember, comme par exemple dans le chapitre : D�finir une donn�e membre transient.

Autre remarque : il est �galement possible d'enregistrer des m�thodes de classe dans le contexte QxOrm (gestion des m�thodes static et non static) avec les m�thodes qx::QxClass<T>::fct_0(), qx::QxClass<T>::fct_1(), etc... Cette fonctionnalit� fait partie du moteur d'introspection de la biblioth�que QxOrm, plus de d�tails dans le chapitre : Appeler dynamiquement une fonction.

Cl� primaire autre que le type par d�faut "long"

Par d�faut, lorsqu'un mapping d'une classe C++ est �crit avec la m�thode void qx::register_class<T>, l'identifiant associ� � la classe est de type long (cl� primaire avec auto-incr�mentation dans la base de donn�es).

Il est possible de d�finir un identifiant d'un autre type en utilisant la macro QX_REGISTER_PRIMARY_KEY.
Cette macro sp�cialise le template qx::trait::get_primary_key<T> pour associer un type d'identifiant � une classe C++.

Par exemple, pour d�finir un identifiant unique de type QString pour la classe C++ myClass (mapp�e vers une table de la BDD avec une colonne de type VARCHAR pour cl� primaire), il suffit d'�crire : QX_REGISTER_PRIMARY_KEY(myClass, QString)

Voici un exemple d'utilisation de la macro QX_REGISTER_PRIMARY_KEY avec une classe author poss�dant un identifiant de type QString :

#ifndef _QX_BLOG_AUTHOR_H_
#define _QX_BLOG_AUTHOR_H_
 
class author
{
public:
// -- propri�t�s
   QString  m_id;
   QString  m_name;
// -- constructeur, destructeur virtuel
   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_


Cl� primaire sur plusieurs colonnes (composite key)

QxOrm supporte la notion de 'multi-columns primary key'.
L'identifiant de la classe doit �tre du type suivant :
  • QPair ou std::pair pour d�finir deux colonnes ;
  • boost::tuple (ou std::tuple) pour d�finir de deux � neuf colonnes.
Il est n�cessaire d'utiliser la macro QX_REGISTER_PRIMARY_KEY() pour sp�cialiser le template et ainsi d�finir le type d'identifiant sur plusieurs colonnes.
La liste des noms des colonnes doit �tre de la forme suivante : 'column1|column2|column3|etc.'.

Exemple d'utilisation avec la classe 'author' du projet 'qxBlogCompositeKey', cette classe poss�de un identifiant sur trois colonnes :

#ifndef _QX_BLOG_AUTHOR_H_
#define _QX_BLOG_AUTHOR_H_

class blog;

class QX_BLOG_DLL_EXPORT author
{

   QX_REGISTER_FRIEND_CLASS(author)

public:

// -- cl� compos�e (cl� primaire d�finie sur plusieurs colonnes dans la base de donn�es)
   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 };

// -- propri�t�s
   type_composite_key   m_id;
   QString              m_name;
   QDate                m_birthdate;
   enum_sex             m_sex;
   list_blog            m_blogX;

// -- constructeur, destructeur virtuel
   author() : m_id("", 0, ""), m_sex(unknown) { ; }
   virtual ~author() { ; }

// -- m�thodes
   int age() const;

// -- m�thodes d'acc�s � la cl� compos�e
   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); }

// -- m�thodes de modification de la cl� compos�e
   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());
}


Donn�es membres public/protected/private

Pour enregistrer des membres private ou protected dans le contexte QxOrm (fonction qx::register_class<T>), il faut d�clarer les friend class n�cessaires.
Pour simplifier l'�criture avec les template C++, la biblioth�que QxOrm fournit la macro suivante : QX_REGISTER_FRIEND_CLASS(myClass).
Un exemple d'utilisation se trouve dans le dossier ./test/qxDllSample/dll1/ du package QxOrm avec la classe CPerson :

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


Espace de nom (namespace)

Si une classe est d�finie dans un espace de nom (namespace), alors une erreur de compilation se produit avec l'utilisation des macros : QX_REGISTER_HPP et QX_REGISTER_CPP. Pour �viter ces erreurs de compilation, il est n�cessaire d'utiliser les macros suivantes : QX_REGISTER_COMPLEX_CLASS_NAME_HPP et QX_REGISTER_COMPLEX_CLASS_NAME_CPP.

Vous trouverez un exemple d'utilisation dans le dossier ./test/qxDllSample/dll1/ de la distribution de QxOrm avec la classe CPerson d�finie dans l'espace de nom qx::test :

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

Les macros QX_REGISTER_COMPLEX_CLASS_NAME... n�cessitent un param�tre suppl�mentaire (dans l'exemple ci-dessus il s'agit du param�tre qx_test_CPerson) afin de cr�er une variable globale.
Celle-ci est appel�e d�s le lancement de l'application.
La construction de cette instance globale d�clare la classe dans le module QxFactory (mod�le de conception fabrique ou design pattern factory).
Un objet C++ ne pouvant pas se nommer avec des caract�res "::", le param�tre suppl�mentaire de la macro permet de remplacer tous les "::" par des "_".

Types C++ support�s par QxOrm

La biblioth�que QxOrm supporte la plupart des types primitifs du standard C++ et du framework Qt (num�riques, bool�ens, chaines de caract�res, date/heure, collections, pointeurs et pointeurs intelligents, etc...). Voici un exemple pr�sentant une liste (non exhaustive) de types C++ support�s ainsi que l'association par d�faut du type de base de donn�es (format SQLite) :

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

Remarque : il est �galement possible de persister un type non g�r� par d�faut par la biblioth�que QxOrm. Rendez-vous au chapitre Persister des types personnalis�s pour plus de d�tails sur cette fonctionnalit�.

Autre remarque : concernant l'association d'un type C++ avec le type de base de donn�es associ�, rendez-vous au chapitre Associer un type SQL � une classe C++ pour plus de d�tails.

D�finir une donn�e membre transient

Une donn�e membre transient n'est pas associ�e � une colonne d'une table de la base de donn�es. Le module QxDao ignore donc cette propri�t� pour toutes les requ�tes � la base de donn�es.

A quoi sert l'enregistrement d'une donn�e membre transient dans le contexte QxOrm ?
Enregistrer une donn�e membre transient dans le contexte QxOrm permet de disposer des autres fonctionnalit�s de la biblioth�que QxOrm sur cette propri�t�, comme par exemple : s�rialisation, introspection, etc...

La m�thode qx::QxClass<T>::data() dispose d'un param�tre optionnel nomm� : bool bDao (par d�faut, valeur � true). Par exemple, ajoutons une propri�t� transient nomm�e age � la classe person (cette propri�t� n'a pas besoin d'�tre stock�e en base de donn�es puisque nous disposons d�j� de la propri�t� birthDate) :

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

Voici une autre fa�on de d�finir une propri�t� transient en r�cup�rant l'instance de type qx::IxDataMember :

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


Connexion � la base de donn�es

La connexion � la base de donn�es peut �tre param�tr�e avec la classe singleton : qx::QxSqlDatabase.
Voici un exemple de param�trage � une base de donn�es SQLite nomm�e 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("");

Une fois les param�tres de connexion renseign�s dans la classe singleton qx::QxSqlDatabase, toutes les op�rations avec la base de donn�es effectu�es par la biblioth�que QxOrm utiliserons ces param�tres. Pour plus d'informations sur les param�tres de connexion � renseigner, il est recommand� de lire la documentation de la classe QSqlDatabase du framework Qt.

Remarque : la classe qx::QxSqlDatabase g�re automatiquement les appels � la base de donn�es dans diff�rents threads (multi-threading).

Autre remarque : il est possible de g�rer son propre pool de connexions � la base de donn�es, et de travailler �galement avec plusieurs bases de donn�es distinctes : rendez-vous dans le chapitre Travailler avec plusieurs bases de donn�es pour plus d'informations sur cette fonctionnalit�.

Autre remarque : suivant le pilote SQL renseign� dans les param�tres de connexion, la biblioth�que QxOrm associe automatiquement un g�n�rateur SQL. Ce g�n�rateur SQL permet de g�rer les sp�cificit�s propres � chaque type de base de donn�es. Tous les g�n�rateurs SQL h�ritent de la classe de base : qx::dao::detail::IxSqlGenerator :
   qx::dao::detail::IxSqlGenerator_ptr pSqlGenerator;
   pSqlGenerator.reset(new qx::dao::detail::QxSqlGenerator_MSSQLServer());   
   qx::QxSqlDatabase::getSingleton()->setSqlGenerator(pSqlGenerator);   


Sauvegarder une instance C++ en base de donn�es (insert/update)

Toutes les fonctions li�es � la base de donn�es sont disponibles dans l'espace de nom qx::dao.

Pour sauvegarder une instance C++ (ou une liste d'instances C++) en base de donn�es, la biblioth�que QxOrm fournit les fonctions suivantes :
  • qx::dao::insert : ins�re une instance (ou une liste d'instances) en base de donn�es ;
  • qx::dao::insert_with_relation : ins�re une instance (ou une liste d'instances) + ses relations en base de donn�es ;
  • qx::dao::insert_with_all_relation : ins�re une instance (ou une liste d'instances) + toutes ses relations en base de donn�es ;

  • qx::dao::update : met � jour une instance (ou une liste d'instances) en base de donn�es ;
  • qx::dao::update_with_relation : met � jour une instance (ou une liste d'instances) + ses relations en base de donn�es ;
  • qx::dao::update_with_all_relation : met � jour une instance (ou une liste d'instances) + toutes ses relations en base de donn�es ;
  • qx::dao::update_by_query : met � jour une instance (ou une liste d'instances) en base de donn�es en filtrant avec une requ�te SQL ;
  • qx::dao::update_by_query_with_relation : met � jour une instance (ou une liste d'instances) + ses relations en base de donn�es en filtrant avec une requ�te SQL ;
  • qx::dao::update_by_query_with_all_relation : met � jour une instance (ou une liste d'instances) + toutes ses relations en base de donn�es en filtrant avec une requ�te SQL ;
  • qx::dao::update_optimized : met � jour uniquement les champs modifi�s d'une instance (ou d'une liste d'instances) en base de donn�es en utilisant le pattern is dirty et les fonctionnalit�s de la classe qx::dao::ptr ;
  • qx::dao::update_optimized_by_query : met � jour uniquement les champs modifi�s d'une instance (ou d'une liste d'instances) en base de donn�es en utilisant le pattern is dirty et les fonctionnalit�s de la classe qx::dao::ptr et en filtrant avec une requ�te SQL ;

  • qx::dao::save : ins�re (si l'�l�ment n'existe pas en base de donn�es) ou met � jour (si l'�l�ment existe d�j� en base de donn�es) ;
  • qx::dao::save_with_relation : ins�re (si l'�l�ment n'existe pas en base de donn�es) ou met � jour (si l'�l�ment existe d�j� en base de donn�es) + ses relations ;
  • qx::dao::save_with_all_relation : ins�re (si l'�l�ment n'existe pas en base de donn�es) ou met � jour (si l'�l�ment existe d�j� en base de donn�es) + toutes ses relations ;
  • qx::dao::save_with_relation_recursive : ins�re (si l'�l�ment n'existe pas en base de donn�es) ou met � jour (si l'�l�ment existe d�j� en base de donn�es) + toutes les relations sur tous les niveaux : utile pour sauvegarder en 1 commande une structure en arbre par exemple.

Par exemple :
   // 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);


Remarque : toutes les fonctions de l'espace de nom qx::dao sont flexibles au niveau des param�tres, elles peuvent accepter : une instance, une liste d'instances, un pointeur, un pointeur intelligent, une liste de pointeurs, une liste de pointeurs intelligents, etc... Par exemple :
  • 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);
Pour connaitre la liste des collections support�es, rendez-vous dans le chapitre : Collections support�es par QxOrm.
Pour connaitre la liste des pointeurs intelligents support�s, rendez-vous dans le chapitre : Pointeurs intelligents support�s par QxOrm (smart-pointers).


Supprimer une instance C++ de la base de donn�es (delete)

Toutes les fonctions li�es � la base de donn�es sont disponibles dans l'espace de nom qx::dao.

Pour supprimer une instance C++ (ou une liste d'instances C++) en base de donn�es, la biblioth�que QxOrm fournit les fonctions suivantes :
Par exemple :
   // 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>();


Suppression logique (soft delete)

Une suppression logique permet de ne pas effacer de ligne dans une table d'une base de donn�es (contrairement � une suppression physique) : une colonne suppl�mentaire est ajout�e � la d�finition de la table pour indiquer que la ligne est supprim�e ou non.
Cette colonne peut contenir soit un bool�en (1 signifie ligne supprim�e, 0 ou vide signifie ligne non supprim�e), soit la date-heure de suppression de la ligne (si vide, la ligne est consid�r�e comme non supprim�e).
Il est donc � tout moment possible de r�activer une ligne supprim�e en r�initialisant la valeur � vide dans la table de la base de donn�es.

Pour activer le m�canisme de suppression logique avec la biblioth�que QxOrm, il faut utiliser la classe qx::QxSoftDelete dans la fonction de mapping qx::register_class<T>.
Voici un exemple d'utilisation avec une classe Bar contenant deux propri�t�s m_id et 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");
}}

Les requ�tes SQL g�n�r�es automatiquement par la biblioth�que QxOrm vont prendre en compte ce param�tre de suppression logique pour ajouter les conditions n�cessaires (ne pas r�cup�rer les �l�ments supprim�s, ne pas supprimer physiquement une ligne, etc.).
Par exemple, si vous ex�cutez les lignes suivantes avec la classe Bar :

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

Vous obtiendrez les traces suivantes :

[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

Remarque : pour supprimer physiquement une ligne de la base de donn�es, il faut utiliser les fonctions : qx::dao::destroy_by_id() et qx::dao::destroy_all().

Autre remarque : il peut �tre int�ressant de d�finir au niveau du SGBD un index sur la colonne deleted_at (ou peu importe le nom que vous donnez) afin d'acc�l�rer l'ex�cution des requ�tes SQL.

R�cup�rer une instance C++ de la base de donn�es (fetch)

Toutes les fonctions li�es � la base de donn�es sont disponibles dans l'espace de nom qx::dao.

Pour valoriser automatiquement les propri�t�s d'une instance C++ (ou d'une liste d'instances C++) en fonction des donn�es d'une table (ou plusieurs tables si des relations sont d�finies) de la base de donn�es, la biblioth�que QxOrm fournit les fonctions suivantes :
  • qx::dao::fetch_by_id : r�cup�re de la base de donn�es l'�l�ment (ou une liste d'�l�ments) associ� � l'id pass� en param�tre ;
  • qx::dao::fetch_by_id_with_relation : r�cup�re de la base de donn�es l'�l�ment (ou une liste d'�l�ments) + ses relations en fonction de l'id pass� en param�tre ;
  • qx::dao::fetch_by_id_with_all_relation : r�cup�re de la base de donn�es l'�l�ment (ou une liste d'�l�ments) + toutes ses relations en fonction de l'id pass� en param�tre ;

  • qx::dao::fetch_all : r�cup�re toutes les entr�es d'une table de la base de donn�es ;
  • qx::dao::fetch_all_with_relation : r�cup�re toutes les entr�es d'une table + ses relations de la base de donn�es ;
  • qx::dao::fetch_all_with_all_relation : r�cup�re toutes les entr�es d'une table + toutes ses relations de la base de donn�es ;

  • qx::dao::fetch_by_query : r�cup�re toutes les entr�es d'une table de la base de donn�es en fonction d'une requ�te SQL ;
  • qx::dao::fetch_by_query_with_relation : r�cup�re toutes les entr�es d'une table de la base de donn�es + ses relations en fonction d'une requ�te SQL ;
  • qx::dao::fetch_by_query_with_all_relation : r�cup�re toutes les entr�es d'une table de la base de donn�es + toutes ses relations en fonction d'une requ�te SQL ;

  • qx::dao::exist : teste l'existence d'un �l�ment (ou d'une liste d'�l�ments) en base de donn�es en fonction de son identifiant (primary key).

Par exemple :
   // 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);


Requ�tes SQL

La biblioth�que QxOrm fournit plusieurs outils pour effectuer des requ�tes � la base de donn�es : Remarque : QxOrm �tant bas� sur le module QtSql de Qt, il est toujours possible de requ�ter la base de donn�es en utilisant la classe QSqlQuery de Qt si les fonctionnalit�s propos�es par QxOrm ne sont pas suffisantes.

Utilisation de la classe qx::QxSqlQuery (ou son alias qx_query)

La classe qx::QxSqlQuery (ou bien son alias qx_query) permet d'interroger la base de donn�es (trier, filtrer, etc.) de deux mani�res diff�rentes : Le principal avantage de la premi�re m�thode (�criture manuelle des requ�tes SQL) est de pouvoir utiliser certaines optimisations sp�cifiques � chaque base de donn�es.
La deuxi�me m�thode (utilisation du code C++ pour g�n�rer la requ�te SQL) permet de mapper automatiquement les param�tres SQL sans utiliser la fonction qx::QxSqlQuery::bind().

Voici un exemple d'utilisation de la classe qx::QxSqlQuery avec �criture manuelle d'une requ�te SQL :

// Construit une requ�te pour r�cup�rer uniquement les 'author' de 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++)
{ /* traitement avec la collection issue de la base de donn�es */ }

La biblioth�que QxOrm supporte trois syntaxes pour l'�criture des param�tres SQL.
Le type de syntaxe peut �tre modifi� de fa�on globale � un projet en utilisant la m�thode suivante : qx::QxSqlDatabase::getSingleton()->setSqlPlaceHolderStyle().
Les trois param�tres possibles pour cette m�thode sont :
  • ph_style_2_point_name : "WHERE author.sex = :sex" (syntaxe par d�faut) ;
  • ph_style_at_name : "WHERE author.sex = @sex" ;
  • ph_style_question_mark : "WHERE author.sex = ?".
Voici le m�me exemple en utilisant les m�thodes C++ de la classe qx::QxSqlQuery (ou bien son alias qx_query) pour g�n�rer la requ�te automatiquement :

// Construit une requ�te pour r�cup�rer uniquement les 'author' de 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++)
{ /* traitement avec la collection issue de la base de donn�es */ }

Cette utilisation de la classe qx::QxSqlQuery pr�sente l'avantage de ne pas avoir � mapper les param�tres de la requ�te, tout en restant tr�s proche de l'�criture manuelle d'une requ�te SQL.
Les param�tres seront automatiquement inject�s en utilisant la syntaxe d�finie de mani�re globale par la m�thode : qx::QxSqlDatabase::getSingleton()->getSqlPlaceHolderStyle().

Voici un exemple pr�sentant diff�rentes m�thodes disponibles avec la classe qx::QxSqlQuery (ou bien son alias qx_query) :

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

Ce qui produira le code SQL suivant pour les bases de donn�es MySQL, PostgreSQL et SQLite (pour Oracle et SQLServer, le traitement de la m�thode limit() est diff�rent) :

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

Voici la liste des fonctions et m�thodes disponibles pour utiliser la classe qx::QxSqlQuery (ou bien son alias qx_query) :

// avec les fonctions du 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>()

// avec la classe qx::QxSession
qx::QxSession::count<T>()
qx::QxSession::fetchByQuery<T>()
qx::QxSession::update<T>()
qx::QxSession::deleteByQuery<T>()
qx::QxSession::destroyByQuery<T>()

// avec la classe qx::QxRepository<T>
qx::QxRepository<T>::count()
qx::QxRepository<T>::fetchByQuery()
qx::QxRepository<T>::update()
qx::QxRepository<T>::deleteByQuery()
qx::QxRepository<T>::destroyByQuery()

Remarque : certaines de ces fonctions ont �galement deux autres param�tres optionnels :
  • const QStringList & columns : pour indiquer la liste des colonnes � r�cup�rer (par d�faut, toutes les colonnes sont r�cup�r�es) ;
  • const QStringList & relation : pour indiquer les jointures (one-to-one, one-to-many, many-to-one et many-to-many d�finies dans la fonction de mapping void qx::register_class<T>()) entre les tables de la base de donn�es (par d�faut, aucune relation).

Appel de proc�dure stock�e ou requ�te SQL personnalis�e

La biblioth�que QxOrm fournit deux fonctions pour appeler une proc�dure stock�e ou une requ�te SQL personnalis�e : Le premier param�tre de ces deux fonctions, de type qx::QxSqlQuery (ou son alias qx_query), correspond � la proc�dure stock�e ou � la requ�te SQL personnalis�e.
Pour plus d'informations sur la classe qx::QxSqlQuery, rendez-vous sur ce chapitre du manuel utilisateur : Utilisation de la classe qx::QxSqlQuery (ou son alias qx_query).

La fonction qx::dao::execute_query<T>() est une fonction template : le type T doit �tre enregistr� dans le contexte QxOrm (fonction qx::register_class<T>).
Toutes les donn�es renvoy�es par la proc�dure stock�e ou la requ�te SQL personnalis�e qui pourront �tre associ�es aux membres des classes C++ (de type T) seront valoris�es automatiquement.
Une recherche automatique est effectu�e sur le nom des champs associ�s aux donn�es.
Voici un exemple d'utilisation (disponible dans le projet qxBlog du package QxOrm) :

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


La fonction qx::dao::call_query() n'est pas une fonction template : les r�sultats de la requ�te doivent �tre parcourus manuellement sur la classe qx::QxSqlQuery (ou qx_query).
Pour r�cup�rer un param�tre de sortie (qui doit �tre pass� � la requ�te en tant que QSql::Out ou QSql::InOut), il suffit d'utiliser la m�thode : QVariant qx::QxSqlQuery::boundValue(const QString & sKey) const;.

Pour parcourir la liste des r�sultats de la requ�te, il faut utiliser les m�thodes suivantes :
  • 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();
Voici un exemple d'utilisation avec la fonction qx::dao::call_query() :

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)

Une transaction est une suite d'op�rations effectu�es comme une seule unit� logique de travail.
Une fois termin�e, la transaction est :
  • soit valid�e (commit), alors toutes les modifications sont faites dans la base de donn�es ;
  • soit annul�e (rollback), alors toutes les modifications ne sont pas enregistr�e.
La classe qx::QxSession de la biblioth�que QxOrm permet de g�rer automatiquement les transactions (validation, annulation) en utilisant le m�canisme C++ RAII :

{ // Ouverture d'un scope o� une session sera instanci�e

  // Cr�ation d'une session : une connexion valide � la BDD est assign�e � la session et une transaction est d�marr�e
  qx::QxSession session;

  // Ex�cution d'une s�rie d'op�rations avec la BDD (en utilisant l'op�rateur += de la classe qx::QxSession et la connexion de la session)
  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());

  // Si la session n'est pas valide (donc une erreur s'est produite) => affichage de la 1�re erreur de la session
  if (! session.isValid()) { qDebug("[QxOrm] session error : '%s'", qPrintable(session.firstError().text())); }

} // Fermeture du scope : la session est d�truite (transaction => commit ou rollback automatique)

Remarque : une session peut d�clencher une exception de type qx::dao::sql_error lorsqu'une erreur se produit (par d�faut, aucune exception n'est d�clench�e). Il est possible de param�trer ce comportement en utilisant :
  • soit le constructeur de la classe qx::QxSession (pour une session en particulier) ;
  • soit le param�tre du singleton qx::QxSqlDatabase::getSingleton()->setSessionThrowable(bool b) (pour toutes les sessions).
Autre remarque : il est important de ne pas oublier de passer la connexion � la base de donn�es de la session � chaque fonction qx::dao::xxx (en utilisant la m�thode session.database()).
De plus, il est possible d'initialiser une session avec sa propre connexion (provenant d'un pool de connexions par exemple) en utilisant le constructeur de la classe qx::QxSession.

La classe qx::QxSession propose �galement des m�thodes de persistance (CRUD), ce qui peut simplifier l'�criture du code C++ suivant les habitudes de programmation.
Voici le m�me exemple en utilisant les m�thodes de la classe qx::QxSession � la place des fonctions du namespace qx::dao :

{ // Ouverture d'un scope o� une session sera instanci�e

  // Cr�ation d'une session : une connexion valide � la BDD est assign�e � la session et une transaction est d�marr�e
  qx::QxSession session;

  // Ex�cution d'une s�rie d'op�rations avec la BDD
  session.insert(my_object);
  session.update(my_object);
  session.fetchById(my_object);
  session.deleteById(my_object);

  // Si la session n'est pas valide (donc une erreur s'est produite) => affichage de la 1�re erreur de la session
  if (! session.isValid()) { qDebug("[QxOrm] session error : '%s'", qPrintable(session.firstError().text())); }

} // Fermeture du scope : la session est d�truite (transaction => commit ou rollback automatique)


Moteur de relations

La biblioth�que QxOrm fournit un puissant moteur de relations permettant de d�finir facilement : Remarque : un tutoriel complet sur les relations bas� sur le projet de test qxBlog (dont les sources sont pr�sentes dans le package QxOrm) est disponible.

one-to-many (1-n)

Une relation one-to-many (1-n) est d�finie par la m�thode : qx::QxClass<T>::relationOneToMany(). Cette m�thode renvoie une instance de la classe qx::IxSqlRelation (classe de base pour toutes les relations) et n�cessite 3 param�tres :
  • V U::* pData : r�f�rence vers la donn�e membre de la classe ;
  • const QString & sKey : cl� unique associ�e � la relation ;
  • const QString & sForeignKey : cl� �trang�re d�finie dans la classe/table li�e.

Par exemple : prenons l'exemple d'un author (une personne) qui peut r�diger plusieurs blog : nous allons ainsi montrer comment d�finir une relation de type one-to-many.
Au niveau base de donn�es, voici les deux tables qui correspondent :

qxBlog.table.author

Fichier author.h :
#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 };
// -- propri�t�s
   QString     m_id;
   QString     m_name;
   QDate       m_birthdate;
   enum_sex    m_sex;
   list_blog   m_blogX;
// -- constructeur, destructeur virtuel
   author() : m_id(0), m_sex(unknown) { ; }
   virtual ~author() { ; }
// -- m�thodes
   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_

Fichier author.cpp :
#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)

Une relation many-to-one (n-1) est d�finie par la m�thode : qx::QxClass<T>::relationManyToOne(). Cette m�thode renvoie une instance de la classe qx::IxSqlRelation (classe de base pour toutes les relations) et n�cessite 2 param�tres :
  • V U::* pData : r�f�rence vers la donn�e membre de la classe ;
  • const QString & sKey : cl� unique associ�e � la relation (correspond � une colonne de la table dans la base de donn�es).

Par exemple : un comment est associ� � un blog et un blog peut contenir plusieurs comment : nous allons ainsi montrer comment d�finir une relation de type many-to-one.
Au niveau base de donn�es, voici les deux tables qui correspondent :

qxBlog.table.comment

Fichier comment.h :
#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;
// -- propri�t�s
   long        m_id;
   QString     m_text;
   QDateTime   m_dt_create;
   blog_ptr    m_blog;
// -- constructeur, destructeur virtuel
   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_

Fichier comment.cpp :
#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)

Une relation many-to-many (n-n) est d�finie par la m�thode : qx::QxClass<T>::relationManyToMany(). Cette m�thode renvoie une instance de la classe qx::IxSqlRelation (classe de base pour toutes les relations) et n�cessite 5 param�tres :
  • V U::* pData : r�f�rence vers la donn�e membre de la classe ;
  • const QString & sKey : cl� unique associ�e � la relation ;
  • const QString & sExtraTable : nom de la table suppl�mentaire permettant de stocker les id de chaque c�t� des relations ;
  • const QString & sForeignKeyOwner : cl� �trang�re d�finie dans la table suppl�mentaire pour repr�senter la classe/table courante ;
  • const QString & sForeignKeyDataType : cl� �trang�re d�finie dans la table suppl�mentaire pour repr�senter la classe/table associ�e � la relation.

Par exemple : une category r�f�rence plusieurs blog et un blog peut appartenir � plusieurs category : nous allons ainsi montrer comment d�finir une relation de type many-to-many. Ce type de relation implique une table suppl�mentaire dans la base de donn�es pour stocker la liste des id de chaque c�t� des relations.
Au niveau base de donn�es, voici les trois tables qui correspondent :

qxBlog.table.category

Fichier category.h :
#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;
// -- propri�t�s
   long        m_id;
   QString     m_name;
   QString     m_desc;
   list_blog   m_blogX;
// -- constructeur, destructeur virtuel
   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_

Fichier category.cpp :
#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)

Une relation one-to-one (1-1) permet de repr�senter 2 entit�s distinctes qui partagent le m�me identifiant en base de donn�es. Une relation one-to-one (1-1) est d�finie par la m�thode : qx::QxClass<T>::relationOneToOne(). Cette m�thode renvoie une instance de la classe qx::IxSqlRelation (classe de base pour toutes les relations) et n�cessite 2 param�tres :
  • V U::* pData : r�f�rence vers la donn�e membre de la classe ;
  • const QString & sKey : cl� unique associ�e � la relation.

Par exemple : prenons l'exemple d'une table person et d'une autre table author : un author est �galement une person, les 2 tables pourraient partager le m�me identifiant en base de donn�es. Au niveau base de donn�es, voici les 2 tables qui correspondent (person_id == author_id) :

qxBlog.table.person


Requ�te SQL avec relations

La biblioth�que QxOrm supporte quatre types de relations pour lier les classes C++ enregistr�es dans le contexte QxOrm : one-to-one, one-to-many, many-to-one et many-to-many.
Pour plus de d�tails sur la d�finition de ces relations, il est conseill� de lire le tutoriel qxBlog.
Nous allons d�tailler dans cette Q&R les diff�rentes m�thodes de r�cup�ration des donn�es (module QxDao, fonctions du namespace qx::dao) : Le premier param�tre des fonctions fetch_by_id_with_relation, fetch_all_with_relation et fetch_by_query_with_relation correspond � la liste des relations � requ�ter.
Cette liste de relations peut contenir les �l�ments suivants :
  • identifiant d'une relation : chaque relation poss�de une cl� d�finie au niveau de la fonction de param�trage qx::register_class<T> ;
  • le mot-cl� "*" signifie "r�cup�rer toutes les relations d�finies dans la fonction de param�trage qx::register_class<T> sur un niveau" ;
  • le mot-cl� "->" signifie jointure de type "LEFT OUTER JOIN" (jointure par d�faut de la biblioth�que QxOrm) ;
  • le mot-cl� ">>" signifie jointure de type "INNER JOIN" entre deux tables.
Remarque : en utilisant le mot-cl� "*" pour indiquer "toutes les relations sur un niveau", les appels suivants sont �quivalents :
  • 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(...).

Exemple : � partir du tutoriel qxBlog, il est possible de r�cup�rer les donn�es suivantes avec une seule requ�te :

1- r�cup�rer un blog et son author ;
2- pour l'author valoris�, r�cup�rer tous les blog qu'il a �crit ;
3- pour chaque blog que l'author a �crit, r�cup�rer tous les comment associ�s.

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

Ce qui g�n�re la requ�te SQL suivante :
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


Autre exemple : il est �galement possible de cr�er une liste de relations � r�cup�rer, comme ceci par exemple :

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

Ce qui g�n�re la requ�te SQL suivante :
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


Autre exemple : pour r�cup�rer toutes les relations pour un niveau donn�, il faut utiliser le mot-cl� "*".
Pour r�cup�rer toutes les donn�es de toutes les relations sur trois niveaux, il faut �crire :

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

Ce qui g�n�re la requ�te SQL suivante :
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

S�lectionner les colonnes des relations � r�cup�rer et d�finition des alias SQL

Il est parfois n�cessaire de ne pas requ�ter toutes les colonnes d'une table par soucis d'optimisation : en effet, s�lectionner les colonnes r�ellement utilis�es par un traitement permet de limiter les flux r�seau entre la base de donn�es et l'application C++, ce qui am�liore les performances.

Concernant les relations, la biblioth�que QxOrm fournit une syntaxe sp�cifique pour s�lectionner les colonnes � r�cup�rer, sous la forme : my_relation { col_1, col_2, etc... }. Si cette syntaxe n'est pas utilis�e, par d�faut, QxOrm r�cup�re toutes les colonnes.

Par exemple : imaginons la requ�te suivante qui permet de r�cup�rer :
  • uniquement la colonne blog_text de la table blog ;
  • uniquement les colonnes name et birthdate de la table author ;
  • uniquement la colonne comment_text de la table comment.
   // 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

Remarque : une autre syntaxe est disponible afin de renseigner les colonnes � ne pas r�cup�rer : my_relation -{ col_1, col_2, etc... }.

Autre remarque : il est �galement possible de d�finir un alias par relation � utiliser dans la requ�te SQL. Ceci est utile pour l'�criture des conditions dans la clause WHERE. Un alias SQL peut �tre d�fini entre les caract�res < >.

Exemple : voici un exemple de fetch avec relations en d�finissant des alias SQL par 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);


Ce qui g�n�re la requ�te SQL suivante :
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


Ajout SQL dans les clauses LEFT OUTER JOIN / INNER JOIN

La classe qx::QxSqlQuery (ou son alias qx_query) dispose de la m�thode suivante :

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


La m�thode qx::QxSqlQuery::addJoinQuery() permet d'ins�rer des sous-requ�tes SQL dans les clauses LEFT OUTER JOIN / INNER JOIN.
Par exemple :

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


Le code C++ ci-dessus va construire la requ�te SQL suivante :

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


Collections support�es par QxOrm

QxOrm supporte de nombreux conteneurs livr�s avec Qt, boost ou la biblioth�que standard std. La biblioth�que QxOrm fournit �galement son propre conteneur, nomm� qx::QxCollection, particuli�rement adapt� pour stocker les donn�es issues d'une base de donn�es. Le d�veloppeur a donc � sa disposition un large choix : QxOrm n'impose aucune contrainte sur l'utilisation des collections.

Collections de Qt

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

Collections de boost

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

Collections fournies par l'espace de nom standard std

  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

Il existe de nombreux container dans les biblioth�ques stl, boost et Qt.
Il est donc l�gitime de se poser cette question : � quoi sert qx::QxCollection<Key, Value> ?
qx::QxCollection<Key, Value> est un nouveau container (bas� sur l'excellente biblioth�que boost::multi_index_container) qui poss�de les fonctionnalit�s suivantes :
  • conserve l'ordre d'insertion des �l�ments dans la liste ;
  • acc�s rapide � un �l�ment par son index : �quivaut � std::vector<T> ou QList<T> par exemple ;
  • acc�s rapide � un �l�ment par une cl� (hash-map) : �quivaut � QHash<Key, Value> ou boost::unordered_map<Key, Value> par exemple ;
  • fonctions de tri sur le type Key et sur le type Value ;
  • thread-safe.
Remarque : qx::QxCollection<Key, Value> est compatible avec la macro foreach fournie par la biblioth�que Qt ainsi que par la macro BOOST_FOREACH fournie par la biblioth�que boost.
Cependant, chaque �l�ment renvoy� par ces deux macros correspond � un objet de type std::pair<Key, Value>.
Pour obtenir un r�sultat 'plus naturel' et plus lisible, il est conseill� d'utiliser la macro _foreach : cette macro utilise BOOST_FOREACH pour tous les container sauf pour qx::QxCollection<Key, Value>.
Dans ce cas, l'�l�ment renvoy� correspond au type Value (voir par la suite l'exemple d'utilisation).
La macro _foreach est donc compatible avec tous les container (stl, Qt, boost, etc.) puisqu'elle utilise la macro BOOST_FOREACH.

Autre Remarque : qx::QxCollection<Key, Value> est particuli�rement adapt� pour recevoir des donn�es issues d'une base de donn�es.
En effet, ces donn�es peuvent �tre tri�es (en utilisant ORDER BY dans une requ�te SQL par exemple), il est donc important de conserver l'ordre d'insertion des �l�ments dans la liste.
De plus, chaque donn�e issue d'une base de donn�es poss�de un identifiant unique. Il est donc int�ressant de pouvoir acc�der � un �l�ment en fonction de cet identifiant unique de mani�re extr�mement rapide (hash-map).

Exemple d'utilisation de la collection qx::QxCollection<Key, Value> :

/* d�finition d'une classe drug avec 3 propri�t�s : 'code', 'name', 'description' */
class drug { public: QString code; QString name; QString desc; };

/* pointeur intelligent associ� � la classe drug */
typedef boost::shared_ptr<drug> drug_ptr;

/* collection de drugs (acc�s rapide � un �l�ment de la collection par la propri�t� 'code') */
qx::QxCollection<QString, drug_ptr> lstDrugs;

/* cr�ation de 3 nouveaux 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";

/* insertion des 3 drugs dans la collection */
lstDrugs.insert(d1->code, d1);
lstDrugs.insert(d2->code, d2);
lstDrugs.insert(d3->code, d3);

/* parcours la collection en utilisant le mot-cl� '_foreach' */
_foreach(drug_ptr p, lstDrugs)
{ qDebug() << qPrintable(p->name) << " " << qPrintable(p->desc); }

/* parcours la collection en utilisant une boucle 'for' */
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);
}

/* parcours la collection en utilisant le style Java avec 'QxCollectionIterator' */
qx::QxCollectionIterator<QString, drug_ptr> itr(lstDrugs);
while (itr.next())
{
   QString code = itr.key();
   qDebug() << qPrintable(itr.value()->name) << " " << qPrintable(itr.value()->desc);
}

/* effectue un tri croissant par cl� (propri�t� 'code') et d�croissant par valeur */
lstDrugs.sortByKey(true);
lstDrugs.sortByValue(false);

/* acc�s rapide � un drug par son 'code' */
drug_ptr p = lstDrugs.getByKey("code2");

/* acc�s rapide � un drug par son index (position) dans la collection */
drug_ptr p = lstDrugs.getByIndex(2);

/* teste si un drug existe dans la collection et si la liste est vide */
bool bExist = lstDrugs.exist("code3");
bool bEmpty = lstDrugs.empty();

/* supprime de la collection le 2�me �l�ment */
lstDrugs.removeByIndex(2);

/* supprime de la collection l'�l�ment avec le code 'code3' */
lstDrugs.removeByKey("code3");

/* efface tous les �l�ments de la collection */
lstDrugs.clear();


Pointeurs intelligents support�s par QxOrm (smart-pointers)

QxOrm supporte de nombreux pointeurs intelligents livr�s avec Qt, boost ou la biblioth�que standard std. La biblioth�que QxOrm fournit �galement son propre pointeur intelligent, nomm� qx::dao::ptr, apportant de nouvelles fonctionnalit�s lorsqu'il est utilis� avec les fonctions de l'espace de nom qx::dao. Le d�veloppeur a donc � sa disposition un large choix : QxOrm n'impose aucune contrainte sur l'utilisation des pointeurs intelligents.

Pointeurs intelligents de Qt

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

Pointeurs intelligents de boost

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

Pointeurs intelligents fournis par l'espace de nom standard std

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

qx::dao::ptr

QxOrm est compatible avec les pointeurs intelligents des biblioth�ques boost et Qt.
Le pointeur intelligent d�velopp� par QxOrm est bas� sur QSharedPointer et apporte de nouvelles fonctionnalit�s s'il est utilis� avec les fonctions 'qx::dao::...'.
qx::dao::ptr<T> conserve automatiquement les valeurs issues de la base de donn�es.
Il est ainsi possible de v�rifier � tout moment si une instance d'objet a subi des modifications gr�ce � la m�thode 'isDirty()' : cette m�thode peut renvoyer la liste de toutes les propri�t�s ayant �t� modifi�es.
qx::dao::ptr<T> peut �galement �tre utilis� par la fonction 'qx::dao::update_optimized()' pour mettre � jour en base de donn�es uniquement les champs modifi�s.
qx::dao::ptr<T> peut �tre utilis� avec un objet simple ou bien avec la plupart des containers : stl, boost, Qt et qx::QxCollection<Key, Value>.

Exemple d'utilisation du pointeur intelligent qx::dao::ptr<T> :

// exemple d'utilisation de la m�thode 'isDirty()'
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("|"))); }

// met � jour uniquement la propri�t� 'm_text' de l'instance 'blog_isdirty'
daoError = qx::dao::update_optimized(blog_isdirty);
qAssert(! daoError.isValid() && ! blog_isdirty.isDirty());
qx::dump(blog_isdirty);

// exemple d'utilisation de la m�thode 'isDirty()' avec une liste d'objets
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("|"))); }

// met � jour la propri�t� 'm_name' en position 1, la propri�t� 'm_birthdate' en position 2 et ne change rien en position 0
daoError = qx::dao::update_optimized(container_isdirty);
qAssert(! daoError.isValid() && ! container_isdirty.isDirty());
qx::dump(container_isdirty);

// r�cup�re uniquement la propri�t� 'm_dt_creation' du 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);


D�clencheurs (triggers)

Les Trigger de QxOrm permettent d'effectuer divers traitements avant et/ou apr�s une insertion, une mise � jour ou bien une suppression dans la base de donn�es.
Un exemple d'utilisation se trouve dans le dossier ./test/qxDllSample/dll2/ avec la classe BaseClassTrigger.
Cette classe contient cinq propri�t�s : m_id, m_dateCreation, m_dateModification, m_userCreation et m_userModification.
Ces propri�t�s se mettront � jour automatiquement pour chaque classe h�ritant de BaseClassTrigger (cf. les classes Foo et Bar du m�me projet).
Il est n�cessaire de sp�cialiser le template 'qx::dao::detail::QxDao_Trigger<T>' pour profiter de cette fonctionnalit�.

#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";
}


Validation d'une instance C++ (validators)

Le module QxValidator de la biblioth�que QxOrm permet d'ajouter des contraintes sur les propri�t�s enregistr�es dans le contexte QxOrm.
Ces contraintes sont d�finies dans la m�thode de mapping : void qx::register_class<T>.
Si pour une instance de classe donn�e, au moins une contrainte n'est pas respect�e, alors l'instance est consid�r�e comme invalide : l'objet ne peut alors pas �tre sauvegard� en base de donn�es (INSERT ou UPDATE).

Il est �galement possible d'utiliser le module QxValidator pour valider les donn�es au niveau de la couche pr�sentation de l'application : si les donn�es saisies par un utilisateur ne sont pas valides, un message d'erreur peut �tre signal�, il n'est alors pas n�cessaire d'essayer d'enregistrer l'instance courante en base de donn�es.
Les r�gles de validation n'ont pas besoin d'�tre dupliqu�es : elles peuvent �tre utilis�es aussi bien par la couche pr�sentation que par la couche d'acc�s aux donn�es de l'application.

Voici la description de quelques classes du module QxValidator :
  • qx::IxValidator : chaque contrainte d�finie dans la fonction de mapping void qx::register_class<T> est associ�e � une interface de type qx::IxValidator ;
  • qx::IxValidatorX : pour une classe donn�e, la liste des contraintes est associ�e � une interface de type qx::IxValidatorX. Cette collection peut �tre parcourue � l'ex�cution du programme : �a peut �tre int�ressant par exemple pour g�n�rer le sch�ma DDL SQL et prendre en compte les contraintes au niveau de la base de donn�es (voir le chapitre suivant du manuel utilisateur : G�n�rer le sch�ma DDL SQL de la base de donn�es) ;
  • qx::QxInvalidValueX : au moment du processus de validation, lorsqu'une instance n'est pas valide, la liste des contraintes non respect�es est repr�sent�e par une collection de type qx::QxInvalidValueX ;
  • qx::QxInvalidValue : chaque �l�ment de cette collection est de type qx::QxInvalidValue et contient un message d'erreur (description expliquant pourquoi l'instance est invalide).
Le module QxValidator g�re automatiquement la notion d'h�ritage de classe : si des contraintes sont d�finies au niveau de la classe de base, alors elles seront automatiquement v�rifi�es pour chaque validation d'une classe d�riv�e.

Voici un exemple d'utilisation du module QxValidator avec une classe 'person' :

* fichier 'person.h' :
#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_

* fichier 'person.cpp' :
#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)
{
   // Cette m�thode est appel�e automatiquement par le module 'QxValidator' :
   // - avant d'ins�rer ou mettre � jour une instance de type 'person' par les fonctions du namespace 'qx::dao' ;
   // - en utilisant la fonction 'qx::validate()' avec pour param�tre une instance de type 'person'.

   // L'enregistrement de la m�thode 'person::isValid()' est effectu� dans la fonction de mapping :
   // pAllValidator->add_CustomValidator(& person::isValid);

   // Dans cette m�thode, il est possible de v�rifier n'importe quelle valeur de l'instance courante
   // Si une propri�t� est non valide, il suffit d'ins�rer un �l�ment dans la collection 'invalidValues'

   // Remarque : cette m�thode est d�clar�e 'private' pour forcer l'utilisateur � utiliser la fonction 'qx::validate()'
   // Mais ce n'est pas une obligation : cette m�thode peut �tre d�clar�e 'public' ou 'protected'

   // Par exemple, si on souhaite v�rifier la propri�t� '_sex' d'une personne :
   if ((_sex != male) && (_sex != female))
   { invalidValues.insert("le sexe de la personne doit �tre d�fini : masculin ou f�minin"); }
}

* fichier 'global_validator.h' :
// Les fonctions suivantes ('validateFirstName()' et 'validateDateTime()') sont globales (non li�es � une classe)
// Elles peuvent ainsi �tre utilis�es par plusieurs classes pour valider une propri�t� (par exemple : valider la saisie d'une adresse IP).
// Ces fonctions seront appel�es automatiquement par le module 'QxValidator' :
// - avant d'ins�rer ou mettre � jour une instance de classe par les fonctions du namespace 'qx::dao' ;
// - en utilisant la fonction 'qx::validate()'.
 
void validateFirstName(const QVariant & value, const qx::IxValidator * validator, qx::QxInvalidValueX & invalidValues)
{
   // Ici, on peut tester la valeur d'une propri�t� (convertie en type QVariant)
   // Si la valeur est invalide, il suffit d'ins�rer un message � la collection 'invalidValues'

   // Par exemple, si la valeur ne doit jamais �tre �gale � "admin" :
   if (value.toString() == "admin")
   { invalidValues.insert("la valeur ne peut pas �tre �gale � 'admin'"); }
}

void validateDateTime(const QDateTime & value, const qx::IxValidator * validator, qx::QxInvalidValueX & invalidValues)
{
   // Ici, on peut tester la valeur d'une propri�t� (en conservant son vrai type, ici il s'agit de tester une date-heure de type 'QDateTime')
   // Si la valeur est invalide, il suffit d'ins�rer un message � la collection 'invalidValues'

   // Par exemple, si la date-heure doit forc�ment �tre renseign�e :
   if (! value.isValid())
   { invalidValues.insert("la date-heure doit �tre renseign�e et doit �tre valide"); }
}

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

A l'ex�cution de ce bout de code, l'instance 'personValidate' est non valide : la collection 'invalidValues' contient quatre �l�ments :
- "la valeur de la propri�t� 'firstName' ne peut pas �tre vide" ;
- "le sexe de la personne doit �tre d�fini : masculin ou f�minin" ;
- "la valeur ne peut pas �tre �gale � 'admin'" ;
- "la date-heure doit �tre renseign�e et doit �tre valide".

Le module QxValidator fournit plusieurs validateurs pour effectuer des v�rifications basiques :
  • add_NotNull() : v�rifie que la valeur n'est pas nulle ;
  • add_NotEmpty() : v�rifie que la cha�ne de caract�res n'est pas vide ;
  • add_MinValue() : v�rifie que la valeur num�rique n'est pas inf�rieure au param�tre ;
  • add_MaxValue() : v�rifie que la valeur num�rique n'est pas sup�rieure au param�tre ;
  • add_Range() : v�rifie que la valeur num�rique est comprise entre les deux param�tres ;
  • add_MinDecimal() : v�rifie que la valeur d�cimale n'est pas inf�rieure au param�tre ;
  • add_MaxDecimal() : v�rifie que la valeur d�cimale n'est pas sup�rieure au param�tre ;
  • add_RangeDecimal() : v�rifie que la valeur d�cimale est comprise entre les deux param�tres ;
  • add_MinLength() : v�rifie que la cha�ne de caract�res a une taille minimale ;
  • add_MaxLength() : v�rifie que la cha�ne de caract�res ne d�passe pas un certain nombre de caract�res ;
  • add_Size() : v�rifie que la taille de la cha�ne de caract�res est comprise entre les deux param�tres ;
  • add_DatePast() : v�rifie que la date-heure est dans le pass� ;
  • add_DateFuture() : v�rifie que la date-heure est dans le futur ;
  • add_RegExp() : v�rifie que la cha�ne de caract�res est compatible avec l'expression r�guli�re pass�e en param�tre ;
  • add_EMail() : v�rifie que la cha�ne de caract�res correspond � un e-mail.
Comme dans l'exemple de la classe 'person', il est possible de d�finir �galement des validateurs personnalis�s : ce sont des fonctions ou m�thodes de classe qui seront appel�es automatiquement par le module QxValidator pour valider une propri�t� ou une instance de classe.
Il existe trois types de validateurs personnalis�s :
  • add_CustomValidator() : m�thode de classe, la signature de la m�thode doit �tre "void my_class::my_method(qx::QxInvalidValueX &)" ;
  • add_CustomValidator_QVariant() : fonction globale avec type QVariant (propri�t� convertie en QVariant), la signature de la fonction doit �tre "void my_validator(const QVariant &, const qx::IxValidator *, qx::QxInvalidValueX &)" ;
  • add_CustomValidator_DataType() : fonction globale avec le type r�el de la propri�t�, la signature de la fonction doit �tre "void my_validator(const T &, const qx::IxValidator *, qx::QxInvalidValueX &)" ;
Remarque : � chaque validateur peut �tre associ� un groupe (param�tre optionnel pour chaque m�thode add_XXX() de la classe qx::IxValidatorX).
Il est ainsi possible de cr�er des groupes de validation suivant le contexte d'ex�cution : par exemple, valider la saisie d'une personne sur une IHM A ne n�cessite peut-�tre pas les m�mes v�rifications que valider une personne sur une IHM B.
Pour ex�cuter la validation d'une instance pour un groupe donn� (par exemple "myGroup"), il faut appeler la fonction suivante : "qx::QxInvalidValueX invalidValues = qx::validate(personValidate, "myGroup");".

Autre remarque : le module QxValidator d�finit des messages par d�faut lorsqu'une contrainte n'est pas v�rifi�e.
Il est possible de red�finir ces messages par d�faut en modifiant la collection suivante : "QHash * lstMessage = QxClassX::getAllValidatorMessage();".
Par exemple : "lstMessage->insert("min_value", "la valeur '%NAME%' doit �tre inf�rieure ou �gale � '%CONSTRAINT%'");".
Les champs %NAME% et %CONSTRAINT% seront automatiquement remplac�s par les valeurs correspondantes.
Pour modifier le message pour un validateur donn� (et non de mani�re globale), il faut utiliser le param�tre optionnel disponible pour les m�thodes add_XXX() de la classe qx::IxValidatorX.

G�rer la valeur NULL de la base de donn�es

Les bases de donn�es poss�dent la notion de valeur NULL : pour plus de d�tails sur la valeur NULL, rendez-vous sur la page Wikipedia.
La biblioth�que QxOrm permet de g�rer la valeur NULL de plusieurs fa�ons diff�rentes :
  • utilisation de la classe boost::optional fournie par boost ;
  • utilisation de la classe QVariant fournie par Qt ;
  • utilisation de pointeurs ou pointeurs intelligents : un pointeur NULL est associ� � la valeur NULL en base de donn�es.

boost::optional

La classe boost::optional<T> fournie par boost est particuli�rement adapt�e pour g�rer la notion de valeur NULL en base de donn�es.
Pour utiliser boost::optional<T> avec la biblioth�que QxOrm, il est n�cessaire de d�finir l'option de compilation _QX_ENABLE_BOOST, ou bien d'inclure l'en-t�te <QxExtras/QxBoostOptionalOnly.h>.
Voici un exemple de classe dont toutes les propri�t�s (sauf la cl� primaire) peuvent �tre NULL en utilisant boost::optional :

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

La classe boost::optional<T> se manipule facilement : rendez-vous sur la documentation fournie par boost pour plus de d�tails.

QVariant

La classe QVariant fournie par Qt permet �galement de g�rer la notion de valeur NULL en base de donn�es.
Voici un exemple de classe dont toutes les propri�t�s (sauf la cl� primaire) peuvent �tre NULL en utilisant QVariant :

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

Cette solution a pour d�savantage de perdre le type de donn�e compar� � boost::optional<T>.
Il est donc recommand� d'utiliser boost::optional<T> pour g�rer la valeur NULL avec la biblioth�que QxOrm.

H�ritage et polymorphisme

On retrouve g�n�ralement dans les diff�rents outils de type ORM trois diff�rentes strat�gies pour g�rer la notion d'h�ritage avec la base de donn�es : QxOrm utilise par d�faut la strat�gie Concrete Table Inheritance (les autres strat�gies ne sont pas fonctionnelles � l'heure actuelle).
De nombreux tutoriaux et forums sont disponibles sur internet pour plus de d�tails sur cette notion d'h�ritage.
Un exemple d'utilisation avec une classe de base se trouve dans le dossier ./test/qxDllSample/dll2/ avec la classe BaseClassTrigger.

Interface qx::IxPersistable (classe abstraite)

L'interface qx::IxPersistable (ou classe abstraite) dispose uniquement de m�thodes virtuelles pures.
Elle permet d'avoir une classe de base commune pour appeler les fonctions de persistance sans conna�tre le type r�el de l'instance courante (notion de polymorphisme).
La biblioth�que QxOrm n'impose pas de travailler avec une classe de base pour enregistrer un type persistant dans le contexte QxOrm, cependant il est parfois utile de disposer d'une interface afin d'�crire des algorithmes g�n�riques.

La classe qx::IxPersistable met � disposition les m�thodes virtuelles suivantes (pour plus d'informations sur ces m�thodes, rendez-vous sur la documentation en ligne de la biblioth�que QxOrm) :

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;

Par exemple, � partir d'une liste de pointeurs de type qx::IxPersistable, il est possible d'enregistrer les �l�ments dans plusieurs tables diff�rentes de la base de donn�es de la fa�on suivante :

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

Pour impl�menter l'interface qx::IxPersistable, il faut :
  • faire h�riter la classe persistante du type qx::IxPersistable ;
  • dans la d�finition de la classe (myClass.h par exemple), ajouter la macro QX_PERSISTABLE_HPP(myClass) ;
  • dans l'impl�mentation de la classe (myClass.cpp par exemple), ajouter la macro QX_PERSISTABLE_CPP(myClass).
Par exemple, impl�menter l'interface qx::IxPersistable pour la classe author du tutoriel qxBlog revient � �crire (les modifications par rapport au code du tutoriel apparaissent en gras) :

#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 };
// -- propri�t�s
   QString     m_id;
   QString     m_name;
   QDate       m_birthdate;
   enum_sex    m_sex;
   list_blog   m_blogX;
// -- constructeur, destructeur virtuel
   author() : m_id(0), m_sex(unknown) { ; }
   virtual ~author() { ; }
// -- m�thodes
   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());
}

Remarque : le projet de test ./test/qxDllSample/dll1/ met � disposition une sorte de 'super classe de base' : la classe qx::QxPersistable impl�mente l'interface qx::IxPersistable et h�rite de QObject.
Le m�canisme SIGNAL-SLOT de Qt peut donc �tre utilis� avec cette classe, ce qui peut �tre int�ressant par exemple pour la notion de d�clencheurs (ou trigger).
La classe qx::QxPersistable met �galement � disposition des m�thodes virtuelles qu'il est possible de surcharger pour g�rer notamment la notion de validation des donn�es avec le module QxValidator.
La classe qx::QxPersistable ne fait pas partie de la distribution de QxOrm, mais il est possible de la copier-coller dans un projet afin de profiter de ses fonctionnalit�s :

Utiliser le pattern C++ PIMPL (Private Implementation idiom ou d-pointer)

D�finition du site cppreference : "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.

Les avantages � utiliser le pattern PIMPL pour d�finir une classe persistente enregistr�e dans le contexte QxOrm :
  • Compilation Firewall : si l'impl�mentation priv�e change, le code client n'a pas besoin d'�tre recompil� ;
  • R�duction des temps de compilation : les fichiers d'en-t�tes (*.h, *.hpp) sont moins volumineux ;
  • Compatibilit� binaire : vous pouvez d�velopper plusieurs versions d'une biblioth�que sans casser la compatibilit� ;
  • R�duction de la taille des ex�cutables g�n�r�s.
Les inconvients du pattern PIMPL :
  • L�g�re baisse des performances : n�cessit� d'utiliser un niveau d'indirection suppl�mentaire avec le pointeur opaque ;
  • N�cessit� d'allouer un pointeur par instance (peut causer des probl�mes de memory fragmentation).

La biblioth�que QxOrm fournit un projet de test o� toutes les classes persistantes sont cod�es en utilisant le pattern PIMPL : qxBlogPImpl (avec gestion des relations).
Il est �galement possible d'utiliser l'application QxEntityEditor pour g�n�rer facilement et automatiquement toutes les classes persistantes d'un projet C++ avec l'option PIMPL.

Exemple de classe persistante C++ enregistr�e dans le contexte QxOrm en utilisant le pattern PIMPL (avec relations 1-n, n-1 et n-n) :

#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; }


Persister des types personnalis�s

La biblioth�que QxOrm permet de persister n'importe quel type, m�me si ce dernier n'est pas enregistr� dans le contexte QxOrm par la m�thode qx::register_class<T>().

Il est n�cessaire d'�crire les fonctions de s�rialisation de la biblioth�que boost, en utilisant la m�thode non intrusive (puisque le code source n'est pas disponible ou ne peut pas �tre modifi�). Pour plus d'informations sur la s�rialisation des donn�es avec la biblioth�que boost, rendez-vous sur le tutoriel de developpez.com.

Par exemple, imaginons une classe 'ExtObject3D' provenant d'une biblioth�que tierce et dont le code source n'est pas disponible ou ne peut pas �tre modifi�. Voici le code n�cessaire pour pouvoir persister une instance de type 'ExtObject3D' en base de donn�es :

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

Le code ci-dessus est suffisant pour persister une instance de type 'ExtObject3D' en base de donn�es : il est ainsi possible d'utiliser une propri�t� de type 'ExtObject3D' dans une classe persistante enregistr�e dans le contexte QxOrm. Cette propri�t� peut �tre mapp�e sur une colonne de type TEXT ou VARCHAR en base de donn�es.

Le comportement par d�faut de la biblioth�que QxOrm est le suivant : l'instance est s�rialis�e au format XML avant d'�tre ins�r�e ou mise � jour en base de donn�es. Ce comportement par d�faut peut �tre utile par exemple si l'on souhaite enregistrer une collection d'objets sans vouloir faire de relation (et donc g�rer une autre table dans la base de donn�es). Par exemple, si l'on utilise une propri�t� de type std::vector<mon_objet> dans une classe persistante sans relation associ�e, la liste d'�l�ments sera automatiquement enregistr�e au format XML en base de donn�es.

Remarque : ce comportement par d�faut peut �tre facilement modifi� pour un type donn�. Le moteur QtSql utilise le type QVariant pour faire le lien entre le code C++ et la base de donn�es. Le type QVariant peut contenir du texte, des valeurs num�riques, du binaire, etc. Il peut donc �tre int�ressant de sp�cialiser le comportement par d�faut (s�rialisation XML) si l'on souhaite stocker des donn�es au format binaire ou bien optimiser les performances (la s�rialisation XML peut �tre couteuse en temps d'ex�cution). Il suffit de proposer (en plus des fonctions de s�rialisation boost) les conversions n�cessaires en QVariant, par exemple avec la classe 'ExtObject3D' :

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)
{ /* Ici je convertis ExtObject3D en 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)
{ /* Ici je convertis QVariant en ExtObject3D */; return qx_bool(true); } };

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


Remarque : Voici un template pour cr�er un type personnalis� persistable :

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


G�n�rer le sch�ma DDL SQL de la base de donn�es

!!! Il est fortement recommand� d'utiliser l'application QxEntityEditor pour g�rer cette probl�matique !!!

La biblioth�que QxOrm ne fournit pas de m�canisme pour g�rer automatiquement la cr�ation et mise � jour des tables dans la base de donn�es.
En effet, la fonction qx::dao::create_table<T> doit �tre utilis�e uniquement pour cr�er des prototypes.
Il est fortement recommand� d'utiliser un outil sp�cifique � chaque SGBD pour cr�er et maintenir les tables de la base de donn�es (par exemple Navicat pour MySql, pgAdmin pour PostgreSQL, SQLite Manager pour SQLite, etc.).
De plus, un outil sp�cifique � chaque SGBD permet d'appliquer certaines optimisations (ajout d'index par exemple).

Cependant, il peut �tre int�ressant pour certaines applications de ne pas avoir � g�rer manuellement les tables de la base de donn�es.
Dans ce cas, il est possible de cr�er une fonction C++ pour parcourir la liste des classes persistantes enregistr�es dans le contexte QxOrm (en utilisant le moteur d'introspection de la biblioth�que) et ainsi cr�er un script SQL de g�n�ration et mise � jour des tables de la base de donn�es.

La biblioth�que QxOrm fournit une fonction C++ cr��e uniquement � titre d'exemple : elle peut donc servir de base de travail pour cr�er sa propre fonction de g�n�ration de script SQL.
Cette fonction se trouve dans le fichier ./src/QxRegister/QxClassX.cpp et se nomme QString qx::QxClassX::dumpSqlSchema().
Elle g�n�re un script SQL et le renvoie sous forme de QString : il est possible d'adapter cette fonction pour g�n�rer un fichier contenant le script SQL ou bien appliquer chaque instruction SQL directement au SGBD.

Voici un exemple d'impl�mentation propos� par dodobibi pour g�rer une base PostgreSQL : cet exemple g�re les �volutions futures de son application (ajout de colonnes dans une table existante, ajout d'index sur une colonne existante, etc.).
Au lancement de l'application, le num�ro de version est indiqu� de la fa�on suivante :

QApplication app(argc, argv);
app.setProperty("DomainVersion", 1); // Version increment�e � chaque compilation et diffusion de l'application

Une table de la base de donn�es permet de stocker le num�ro de version courant.
Une classe persistante C++ est mapp�e sur cette table de la fa�on suivante :

#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");
}}

Avec la classe DatabaseVersion, il est possible de v�rifier si la version de la base de donn�es est � jour.
C'est le r�le de la fonction isDatabaseVersionOld() :

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

Si au lancement de l'application, la fonction isDatabaseVersionOld() renvoie true, alors la mise � jour de la base de donn�es est effectu�e de la fa�on suivante :

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

    // On se connecte avec un utilisateur de la base de donn�es qui a les droits de modifications du sch�ma
    QSqlDatabase db = qx::QxSqlDatabase::getSingleton()->getDatabaseCloned();
    db.setUserName("MyAdminLogin");
    db.setPassword("MyAdminPassword");

    // On s'assure que la session d�marre une transaction et l�ve une exception � la moindre erreur
    qx::QxSession session(db, true, true);

    // On "fetch" la version de la base de donn�es avec un verrou pour �viter les modifications concurrentes !
    // Si plusieurs utilisateurs lancent l'application en m�me temps et qu'une mise � jour
    // est n�cessaire, le premier fera la mise � jour, et les autres seront en attente
    DatabaseVersion dbVersion;
    session.fetchByQuery(qx_query("WHERE name='MyAppName' FOR UPDATE"), dbVersion);

    // Pour les autres utilisateurs, une fois le verrou lev�, on v�rifie si la mise � jour est toujours n�cessaire
    if (dbVersion.version >= domainVersion) { return; }

    // On ex�cute chaque instruction SQL avec la variable "query"
    QSqlQuery query(db);

    // On r�cup�re toutes les classes persistantes C++ enregistr�es dans le contexte QxOrm
    qx::QxCollection<QString, qx::IxClass *> * pAllClasses = qx::QxClassX::getAllClasses();
    if (! pAllClasses) { qAssert(false); return; }

    // on r�cup�re la liste des tables existantes dans la base (fonction de Qt)
    QStringList tables = db.tables();

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

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

      // Filtre les classes � jour : si la version de pClass est <= � la version enregistr�e dans la base, la mise � jour n'est pas n�cessaire
      if (pClass->getVersion() <= dbVersion.version) { continue; }

      // On cr�e la table si elle n'existe pas, et on d�finit son propri�taire
      if (! tables.contains(pClass->getName()))
      {
        query.exec("CREATE TABLE " + pClass->getName() + " ( ) WITH (OIDS = FALSE);"
                   "ALTER TABLE " + pClass->getName() + " OWNER TO \"MyAdminLogin\";");
        session += query.lastError();
      }

      // On ajoute les colonnes � la table si elles n'existent pas
      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
        {
          // $$ceci est un texte ne n�cessitant pas de caract�res d'�chappement dans postgres grace aux doubles dolars$$
          query.exec("COMMENT ON COLUMN " + pClass->getName() + "." + p->getName() + " IS $$" + p->getDescription() + "$$ ;");
          session += query.lastError();
        }
      }
    }

    // On enregistre la version courante de la base de donn�es
    dbVersion.version = domainVersion;
    session.save(dbVersion);

    // Fin du block "try" : la session est d�truite => commit ou rollback automatique
    // De plus, un commit ou rollback sur la transaction l�ve automatiquement le verrou pos� pr�c�demment
  }
  catch (const qx::dao::sql_error & err)
  {
    QSqlError sqlError = err.get();
    qDebug() << sqlError.databaseText();
    qDebug() << sqlError.driverText();
    qDebug() << sqlError.number();
    qDebug() << sqlError.type();
  }
}

Remarque : le code pr�c�dent (tout comme la fonction qx::QxClassX::dumpSqlSchema()) peut �tre modifi� pour s'adapter aux besoins sp�cifiques d'une application.
Par exemple, il pourrait �tre int�ressant de cr�er par d�faut une seconde table (en plus de la table DatabaseVersion) pour enregistrer la liste des classes persistantes enregistr�es dans le contexte QxOrm : ainsi, au lieu d'utiliser la fonction propos�e par Qt "db.tables()", il serait possible de r�cup�rer toutes les tables mapp�es sur des classes persistantes avec des informations suppl�mentaires (num�ro de version pour chaque table, nombre de colonnes enregistr�es dans le contexte QxOrm, description de chaque table, etc.).

Associer un type SQL � une classe C++

Chaque base de donn�es propose des types SQL diff�rents pour stocker l'information.
La biblioth�que QxOrm propose une association par d�faut pour les classes C++ les plus fr�quemment utilis�es dans un programme.
Voici cette association par d�faut :

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

Si le type SQL propos� par d�faut par la biblioth�que QxOrm ne correspond pas � la base de donn�es utilis�e, il peut facilement �tre modifi� (de mani�re globale � toute l'application) en utilisant la collection suivante :

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

Pour modifier le type SQL de mani�re sp�cifique pour une colonne d'une table de la base de donn�es, il faut d�finir le type SQL dans la fonction de mapping de QxOrm :

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

Pour les classes non support�es par d�faut par la biblioth�que QxOrm (voir ce chapitre du manuel utilisateur : Persister des types personnalis�s), il est possible d'associer un type SQL par d�faut en utilisant la macro suivante (en dehors de tout namespace) :

QX_REGISTER_TRAIT_GET_SQL_TYPE(MyClass, "my_sql_type")


Effectuer des requ�tes asynchrones � la base de donn�es

Il peut �tre parfois int�ressant d'ex�cuter certaines requ�tes � la base de donn�es de mani�re asynchrone (multi-thread), par exemple pour �viter de bloquer une IHM si une requ�te est trop longue � s'ex�cuter.
Pour simplifier les requ�tes asynchrones, la biblioth�que QxOrm fournit la classe qx::QxDaoAsync.
Cette classe ex�cute une requ�te dans un thread d�di� et renvoie un SIGNAL queryFinished() lorsque la requ�te est termin�e.
Pour utiliser la classe qx::QxDaoAsync, il suffit de :
  • v�rifier que la requ�te fait appel � une classe qui impl�mente l'interface qx::IxPersistable ;
  • cr�er une instance de type qx::QxDaoAsync (par exemple, une propri�t� membre d'une classe d�rivant du type QWidget) ;
  • connecter un SLOT au SIGNAL qx::QxDaoAsync::queryFinished() (par exemple, un SLOT d�fini dans une classe d�rivant du type QWidget) ;
  • ex�cuter une requ�te � la base de donn�es en utilisant l'une des m�thodes commen�ant par async : qx::QxDaoAsync::asyncXXXX().
Voici un exemple d'utilisation avec une classe nomm�e MyWidget :

class MyWidget : public QWidget
{ Q_OBJECT

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

};

Et voici l'impl�mentation de la classe MyWidget :

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;
   //...
}


Gestion du cache pour sauvegarder des instances C++ (module QxCache)

Le cache propos� par la biblioth�que QxOrm (module QxCache) est thread-safe et permet de stocker facilement n'importe quel type de donn�es.
Les fonctions pour acc�der au cache se trouvent dans le namespace qx::cache.
Le cache permet une optimisation du programme : il est possible par exemple de stocker des �l�ments issus d'une requ�te effectu�e en base de donn�es.

Chaque �l�ment stock� dans le cache est associ� � une cl� de type QString : cette cl� permet de retrouver rapidement un �l�ment du cache.
Si un nouvel �l�ment est stock� dans le cache avec une cl� qui existe d�j�, alors l'ancien �l�ment associ� � cette cl� est effac� automatiquement du cache.

Le cache de la biblioth�que QxOrm ne g�re pas la dur�e de vie des objets : il n'y a aucun delete effectu� par le cache.
C'est pourquoi il est fortement recommand� (mais ce n'est pas une obligation) de privil�gier le stockage de pointeurs intelligents : par exemple, boost::shared_ptr<T> pour la biblioth�que boost ou bien QSharedPointer<T> pour la biblioth�que Qt.

Le cache peut avoir un co�t relatif maximum pour �viter une utilisation de la m�moire trop importante : chaque �l�ment ins�r� dans le cache peut indiquer un co�t repr�sentant une estimation de sa taille m�moire (par exemple, le nombre d'�l�ments d'une collection).
Lorsque le co�t maximum du cache est atteint, les premiers �l�ments ins�r�s dans le cache sont supprim�s (en respectant l'ordre d'insertion dans le cache) jusqu'� ce que la limite du cache ne soit plus d�pass�e.

Il est possible d'associer � chaque �l�ment du cache une date-heure d'insertion.
Si aucune date-heure n'est renseign�e, alors la date-heure courante est prise en compte.
Ce m�canisme permet de v�rifier si un �l�ment stock� dans le cache n�cessite une mise � jour ou non.

Voici un exemple d'utilisation du cache de la biblioth�que QxOrm (fonctions du namespace qx::cache) :

// D�fini le co�t maximum du cache � 500
qx::cache::max_cost(500);

// R�cup�re une liste de 'author' de la base de donn�es
boost::shared_ptr< QList<author> > list_author;
QSqlError daoError = qx::dao::fetch_all(list_author);

// Ins�re la liste de 'author' dans le cache
qx::cache::set("list_author", list_author);

// R�cup�re une liste de 'blog' de la base de donn�es
QSharedPointer< std::vector<blog> > list_blog;
daoError = qx::dao::fetch_all(list_blog);

// Ins�re la liste de 'blog' dans le cache (co�t = nombre de 'blog')
qx::cache::set("list_blog", list_blog, list_blog.count());

// Pointeur vers un objet de type 'comment'
comment_ptr my_comment;
my_comment.reset(new comment(50));
daoError = qx::dao::fetch_by_id(my_comment);

// Ins�re le 'comment' dans le cache en pr�cisant une date-heure d'insertion
qx::cache::set("comment", my_comment, 1, my_comment->dateModif());

// R�cup�re la liste de 'blog' stock�e dans le cache
list_blog = qx::cache::get< QSharedPointer< std::vector<blog> > >("list_blog");

// R�cup�re la liste de 'blog' sans pr�ciser le type
qx_bool bGetOk = qx::cache::get("list_blog", list_blog);

// Supprime du cache la liste de 'author'
bool bRemoveOk = qx::cache::remove("list_author");

// Compte le nombre d'�l�ments du cache
long lCount = qx::cache::count();

// R�cup�re le co�t actuel des �l�ments stock�s dans le cache
long lCurrentCost = qx::cache::current_cost();

// V�rifie qu'un �l�ment associ� � la cl� "comment" existe dans le cache
bool bExist = qx::cache::exist("comment");

// R�cup�re le 'comment' stock� dans le cache avec sa date-heure d'insertion
QDateTime dt;
bGetOk = qx::cache::get("comment", my_comment, dt);

// Vide le cache
qx::cache::clear();


Travailler avec plusieurs bases de donn�es

Dans le chapitre Connexion � la base de donn�es, nous avons vu comment param�trer la connexion par d�faut avec la classe singleton : qx::QxSqlDatabase. La biblioth�que QxOrm �tant bas�e sur le moteur QtSql de Qt, elle utilise en interne la classe QSqlDatabase de Qt. Toutes les fonctions d'acc�s � la base de donn�es (namespace qx::dao, classe qx::QxSession, etc...) ont un param�tre optionnel : QSqlDatabase * pDatabase = NULL :
  • si la valeur de ce param�tre est � NULL (valeur par d�faut) : alors la biblioth�que QxOrm utilise la classe singleton qx::QxSqlDatabase pour se connecter � la base de donn�es (avec gestion automatique du multi-threading) ;
  • si la valeur est non nulle : alors la biblioth�que QxOrm utilise la connexion fournie par le pointeur QSqlDatabase * pDatabase.
Ce param�tre permet donc de g�rer son propre pool de connexions � une ou plusieurs bases de donn�es.

D�clarer une classe abstraite dans le contexte QxOrm

Une classe abstraite C++ (contenant au moins une m�thode virtuelle pure) ne peut pas �tre mapp�e avec une table d'une base de donn�es (puisqu'elle ne peut pas �tre instanci�e).
Cependant, il peut �tre int�ressant de d�finir une classe abstraite contenant une liste de propri�t�s utilis�es par plusieurs objets persistants.
Un exemple de classe abstraite se trouve dans le dossier ./test/qxDllSample/dll2/ de la distribution de QxOrm avec la classe BaseClassTrigger.
QxOrm propose le m�canisme suivant pour d�finir une classe abstraite dans le contexte QxOrm :
  • d�clarer la classe avec la m�thode 'void register_class' comme n'importe qu'elle autre classe ;
  • utiliser la macro QX_REGISTER_ABSTRACT_CLASS(className) juste apr�s la d�finition de la classe.

D�clarer automatiquement les m�ta-propri�t�s de Qt (macro Q_PROPERTY)

Toute classe h�ritant du type QObject peut d�clarer ses propri�t�s avec la macro Q_PROPERTY : les propri�t�s deviennent alors des m�ta-propri�t�s. Ce m�canisme permet au framework Qt de proposer un moteur d'introspection gr�ce au pr�-compilateur moc. Les m�ta-propri�t�s peuvent alors �tre utilis�es par exemple par le moteur QML, QtScript, etc.

La biblioth�que QxOrm n�cessite une d�claration de chacune des propri�t�s d'une classe dans la fonction de mapping void qx::register_class<T>() afin de proposer l'ensemble de ses fonctionnalit�s (persistance des donn�es, s�rialisation XML, JSON et binaire, etc.). Il est possible de d�clarer automatiquement dans le contexte QxOrm l'ensemble des m�ta-propri�t�s sans maintenir une fonction de mapping void qx::register_class<T>() : la macro QX_REGISTER_ALL_QT_PROPERTIES() utilise le moteur d'introspection de Qt pour parcourir la liste des m�ta-propri�t�s.

Voici un exemple d'utilisation avec la classe TestQtProperty se trouvant dans le dossier ./test/qxDllSample/dll1/include/ de la distribution QxOrm :

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

Pour ceux qui ne souhaitent pas utiliser la macro QX_REGISTER_ALL_QT_PROPERTIES, il est possible d'�crire � la place les quatre lignes de code suivantes :

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

Remarque : le deuxi�me param�tre de la macro QX_REGISTER_ALL_QT_PROPERTIES permet d'indiquer la propri�t� qui servira de cl� primaire dans la base de donn�es. Si ce param�tre est vide, cela signifie que la classe ne poss�de pas de cl� primaire ou bien que celle-ci est d�finie dans une classe de base.

Toute propri�t� d�finie avec la macro Q_PROPERTY peut s'enregistrer dans le contexte QxOrm de deux mani�res diff�rentes :
1- par la m�thode classique : t.data(& MyQObject::my_property, "my_property", 0);
2- ou bien sans mentionner le pointeur vers la donn�e membre de la classe : t.data("my_property", 0);

Peu importe la m�thode d'enregistrement des propri�t�s dans le contexte QxOrm, elles seront accessibles par la m�me interface qx::IxDataMember et proposent donc les m�mes fonctionnalit�s. Il est possible d'utiliser les deux m�thodes dans une m�me fonction de mapping void qx::register_class<T>(). Chaque m�thode d'enregistrement pr�sente des avantages et inconv�nients.

Voici la liste des avantages de la deuxi�me m�thode d'enregistrement des propri�t�s dans le contexte QxOrm :
  • temps de compilation du projet beaucoup plus rapide ;
  • taille de l'ex�cutable g�n�r� plus petite ;
  • forte int�gration avec le moteur d'introspection du framework Qt ;
  • pas besoin de maintenir la fonction de mapping en utilisant la macro QX_REGISTER_ALL_QT_PROPERTIES.
Voici les inconv�nients par rapport � la m�thode classique d'enregistrement des propri�t�s :
  • n�cessite un h�ritage de la classe QObject pour pouvoir utiliser la macro Q_PROPERTY ;
  • ex�cution du programme plus lente (utilisation du type QVariant � la place des template C++) ;
  • ne supporte pas la notion de relation entre tables de la base de donn�es (one-to-one, one-to-many, many-to-one et many-to-many) ;
  • pas d'acc�s au pointeur sur la donn�e membre de la classe (conversion n�cessaire au type QVariant pour acc�der et modifier une valeur).

S�rialisation

La s�rialisation est un m�canisme permettant de sauvegarder l'�tat d'une instance d'objet dans un flux (fichier, r�seau, etc...) sous un certain format (binaire, XML, JSON, texte, etc...). La d�s�rialisation est le processus inverse permettant de restaurer l'�tat d'un objet � partir d'un flux. Pour plus d'informations sur la notion de s�rialisation : rendez-vous sur la page Wikipedia.

Toute classe C++ enregistr�e dans le contexte QxOrm peut �tre s�rialis�e dans diff�rents formats : Remarque : le moteur de s�rialisation de la biblioth�que QxOrm permet de proposer des fonctionnalit�s suppl�mentaires comme le clonage d'entit�s, le dump d'entit�s (format XML ou JSON) ou encore le module QxService.

Autre remarque : par d�faut, toutes les propri�t�s enregistr�es dans le contexte QxOrm sont s�rialisables. Pour supprimer une propri�t� du moteur de s�rialisation, il est possible d'�crire :

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


N� version pour assurer une compatibilit� ascendante

La compatibilit� ascendante permet � une application de pouvoir d�s�rialiser un flux provenant d'une version ant�rieure. La biblioth�que QxOrm impose un num�ro de version par classe ainsi qu'un num�ro de version pour chaque propri�t� enregistr�e dans le contexte QxOrm afin de pouvoir assurer une compatibilit� ascendante automatiquement.

Par exemple, imaginons une classe person cr��e dans une application en version A : nous renseignons dans la macro QX_REGISTER_HPP une n� de version � 0 (correspond � la 1�re version de notre classe person), ainsi qu'un n� de version � 0 pour chacune des propri�t�s de la classe (si param�tre non renseign�, 0 est la valeur par d�faut). Ce qui donne le r�sultat suivant :

* Fichier person.h :
#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_

* Fichier person.cpp :
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);
}}


Dans la version B de notre application, nous modifions la classe person pour ajouter 2 nouvelles propri�t�s : sex et address. Notre classe ayant �volu�e, il faut donc incr�menter son n� de version, et les nouvelles propri�t�s doivent avoir un n� de version � 1, ce qui donne :

* Fichier person.h :
#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_

* Fichier person.cpp :
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);
}}


Remarque : en proc�dant ainsi, la biblioth�que QxOrm peut s�rialiser une instance de la classe person dans une application en version A, puis d�s�rialiser � partir de ce flux issu de la version A afin de recr�er une instance de la classe person dans une version B de l'application.

Autre remarque : la suppression d'une propri�t� casse la compatibilit� ascendante. Il est donc recommand� de ne jamais supprimer de propri�t� pour utiliser le moteur de s�rialisation : il est possible par exemple de mettre une visibilit� � private et de supprimer les accesseurs get/set, la propri�t� devenant ainsi inaccessible � l'ext�rieur de la classe, elle peut alors �tre consid�r�e comme �tant obsol�te.

Moteur QDataStream de Qt

Toute classe C++ enregistr�e dans le contexte QxOrm peut �tre s�rialis�e en utilisant le moteur QDataStream de Qt. Les fonctions pour utiliser ce type de s�rialisation sont disponibles dans l'espace de nom : namespace qx::serialization::qt. Remarque : la s�rialisation QDataStream est portable (s�rialisation/d�s�rialisation compatible sur tous types d'environnement : Windows, Linux, Mac OS X, etc...). Le flux s�rialis� est au format binaire : la taille du flux est donc r�duite (compar� � un flux XML par exemple). La s�rialisation QDataStream �tant bas�e sur le moteur d'introspection de la biblioth�que QxOrm, elle est moins performante que les s�rialisations bas�es sur le moteur boost::serialization.

Par exemple :
   // 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);

Remarque : dans l'exemple ci-dessus, nous s�rialisons une instance C++. Toutes les fonctions du namespace qx::serialization peuvent �galement s�rialiser des listes d'instances C++. Pour connaitre la liste des collections support�es, rendez-vous dans le chapitre : Collections support�es par QxOrm.

Moteur JSON de Qt

Toute classe C++ enregistr�e dans le contexte QxOrm peut �tre s�rialis�e en utilisant le moteur QJson de Qt (n�cessite Qt5). Les fonctions pour utiliser ce type de s�rialisation sont disponibles dans l'espace de nom : namespace qx::serialization::json. Remarque : le moteur de s�rialisation JSON est le plus permissif (compar� au moteur XML par exemple) : en effet, les propri�t�s d'une instance peuvent �tre d�finies dans n'importe quel ordre, les propri�t�s peuvent �tre supprim�es ou ajout�es. La d�s�rialisation JSON ne retourne jamais d'erreur : elle ignore tout si le format des donn�es est incorrect (le flux JSON doit par contre �tre valide) ou bien si des propri�t�s sont absentes : le moteur JSON est donc beaucoup plus flexible que le moteur XML.

Autre remarque : la s�rialisation JSON est bas�e sur le moteur d'introspection de la biblioth�que QxOrm, elle est moins performante que les s�rialisations bas�es sur le moteur boost::serialization.

Par exemple :
   // 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");

L'exemple ci-dessus g�n�re le flux JSON suivant :
{
    "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
    }
}


Remarque : le module QxRestApi de la biblioth�que QxOrm est bas� sur le moteur de s�rialisation JSON.

Autre remarque : il est possible de personnaliser le format de sortie JSON (filtrer les propri�t�s du flux JSON g�n�r� par la s�rialisation). Les fonctions de s�rialisation JSON dispose d'un param�tre optionnel de type QString nomm� format. Les pr�-requis pour utiliser le param�tre format sont :
  • le param�tre format doit �tre pr�fix� par : filter: ;
  • les propri�t�s � exporter sont d�finies entre { } ;
  • les relations sont s�par�es par le caract�re | ;
  • il est possible d'utiliser le caract�re * pour d�finir : toutes les relations sur 1 niveau ;
  • le caract�re - devant les { } signifie : toutes les propri�t�s sauf.

Exemple : voici un exemple de s�rialisation JSON en d�finissant un format de sortie pour filtrer certaines propri�t�s :

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


Moteur XML de boost::serialization

Le moteur XML de boost::serialization n'est pas activ� par d�faut : pour activer cette fonctionnalit�, il est n�cessaire de d�finir les options de compilation _QX_ENABLE_BOOST_SERIALIZATION et _QX_ENABLE_BOOST_SERIALIZATION_XML dans le fichier de configuration QxOrm.pri (ou QxOrm.cmake). Il est �galement n�cessaire de compiler le binaire boost::serialization (ce module de boost n'�tant pas header only), et de renseigner le chemin d'acc�s � ce binaire dans les variables QX_BOOST_LIB_PATH, QX_BOOST_LIB_SERIALIZATION_DEBUG et QX_BOOST_LIB_SERIALIZATION_RELEASE du fichier de configuration QxOrm.pri (ou QxOrm.cmake).

Toute classe C++ enregistr�e dans le contexte QxOrm peut �tre s�rialis�e en utilisant le moteur XML de boost::serialization. Les fonctions pour utiliser ce type de s�rialisation sont disponibles dans l'espace de nom : namespace qx::serialization::xml (m�mes fonctions que dans l'espace de nom qx::serialization::qt).

Ce type de s�rialisation poss�de les caract�ristiques suivantes :
  • portable : compatible sur tous types d'environnement : Windows, Linux, Mac OS X, etc... ;
  • slowest : plus lente que les s�rialisations binary et text ;
  • largest : taille des flux g�n�r�s plus importante que les s�rialisations binary et text ;
  • human-readable : un flux XML peut facilement �tre analys� et lu par un �diteur externe ou un �tre humain.

Moteur binaire de boost::serialization

Le moteur binaire de boost::serialization n'est pas activ� par d�faut : pour activer cette fonctionnalit�, il est n�cessaire de d�finir les options de compilation _QX_ENABLE_BOOST_SERIALIZATION et _QX_ENABLE_BOOST_SERIALIZATION_BINARY dans le fichier de configuration QxOrm.pri (ou QxOrm.cmake). Il est �galement n�cessaire de compiler le binaire boost::serialization (ce module de boost n'�tant pas header only), et de renseigner le chemin d'acc�s � ce binaire dans les variables QX_BOOST_LIB_PATH, QX_BOOST_LIB_SERIALIZATION_DEBUG et QX_BOOST_LIB_SERIALIZATION_RELEASE du fichier de configuration QxOrm.pri (ou QxOrm.cmake).

Toute classe C++ enregistr�e dans le contexte QxOrm peut �tre s�rialis�e en utilisant le moteur binaire de boost::serialization. Les fonctions pour utiliser ce type de s�rialisation sont disponibles dans l'espace de nom : namespace qx::serialization::binary (m�mes fonctions que dans l'espace de nom qx::serialization::qt).

Ce type de s�rialisation poss�de les caract�ristiques suivantes :
  • non-portable : un flux s�rialis� sur un environnement Windows peut �tre incompatible si d�s�rialisation sur un environnement Linux par exemple : il est donc fortement recommand� de rester sur le m�me environnement ;
  • fastest : plus rapide que les s�rialisations XML et text ;
  • smallest : taille des flux g�n�r�s r�duite compar� aux s�rialisations XML et text ;
  • non-human-readable : un flux binaire n'est pas lisible (pas de log possible par exemple).

Autres types de s�rialisation propos�s par boost

Le moteur boost::serialization propose d'autres types de s�rialisation. Ces diff�rents types ne sont pas activ�s par d�faut, pour utiliser ces fonctionnalit�s (m�mes fonctions que dans l'espace de nom qx::serialization::qt), il est n�cessaire de d�finir les options de compilation suivantes dans le fichier de configuration QxOrm.pri (ou QxOrm.cmake) :

Cloner une instance C++

Toute classe C++ enregistr�e dans le contexte QxOrm peut �tre clon�e en utilisant une des fonctions suivantes : Par exemple :

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

Remarque importante : il faut faire attention lorsqu'on clone un pointeur intelligent (boost::shared_ptr ou QSharedPointer par exemple) dont l'�l�ment parent (root) peut �tre r�f�renc� plusieurs fois dans sa hi�rarchie (cas d'une structure en arbre par exemple). Dans ce cas, afin de prot�ger le pointeur parent d'une double suppression (2 pointeurs intelligents qui pointent sur le m�me pointeur brut), il est conseill� de cloner de cette fa�on :

// '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' ...


Afficher le d�tail d'une instance C++ (dump au format XML ou JSON)

Toute instance C++ enregistr�e dans le contexte QxOrm peut �tre affich�e au format JSON. Si le moteur XML de boost::serialization est activ�, alors il est �galement possible d'afficher un dump sous format XML (param�tre d'entr�e de la fonction qx::dump). Cette fonctionnalit� peut �tre utile pour faire du d�bogage par exemple, ou bien pour g�n�rer des logs.

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

Ce qui g�n�re le flux XML suivant :

[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 - R�flexion

Toute classe enregistr�e dans le contexte QxOrm par la m�thode qx::register_class<T>() peut �tre utilis�e par le moteur d'introspection (ou r�flexion) de la biblioth�que QxOrm. Le moteur d'introspection permet d'obtenir de fa�on dynamique (donc pendant l'ex�cution du programme) des informations propres � un type. Ces informations correspondent � des m�ta-donn�es et d�crivent de fa�on exhaustive les caract�ristiques d'une classe (propri�t�s, m�thodes, etc.). De nombreux langages de programmation (par exemple Java ou C#) int�grent nativement ce m�canisme, ce n'est pas le cas du C++, c'est pourquoi la biblioth�que QxOrm �mule un moteur d'introspection. Pour plus de d�tails sur l'introspection (ou r�flexion), rendez-vous sur la page Wikipedia.

Voici la liste des classes disponibles pour acc�der aux m�ta-donn�es :
  • qx::QxClassX : singleton permettant de parcourir l'ensemble des classes enregistr�es dans le contexte QxOrm par la m�thode qx::register_class<T>() ;
  • qx::IxClass : interface pour une classe enregistr�e dans le contexte QxOrm ;
  • qx::IxDataMemberX : liste des propri�t�s associ�es � une classe ;
  • qx::IxDataMember : interface pour une propri�t� d'une classe ;
  • qx::IxFunctionX : liste des m�thodes associ�es � une classe ;
  • qx::IxFunction : interface pour une m�thode d'une classe (static ou non static).
Une instance de type qx::IxClass poss�de la liste des propri�t�s d'une classe (qx::IxDataMemberX) ainsi que la liste des m�thodes d'une classe (qx::IxFunctionX).

Le moteur d'introspection de la biblioth�que QxOrm permet par exemple de :
Remarque : le module QxService de la biblioth�que QxOrm (cliquez ici pour acc�der au tutoriel) permettant de cr�er un serveur d'applications C++ est bas� sur le moteur d'introspection pour appeler dynamiquement les m�thodes de type service (demande du client) sur le serveur, ainsi que pour cr�er dynamiquement les instances des classes de param�tre (entr�e/sortie).

Autre remarque : il est possible d'ajouter de nouvelles informations au moteur d'introspection en utilisant la notion de property bag. En effet, les classes qx::IxClass, qx::IxDataMember et qx::IxFunction poss�dent chacune une liste d'�l�ments de type QVariant accessibles par cl� de type QString (voir la classe qx::QxPropertyBag pour plus de d�tails sur cette notion).

Autre remarque : afin d'initialiser le moteur d'introspection QxOrm, il est recommand� d'appeler la fonction suivante en d�but de programme (main par exemple) :

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


Obtenir dynamiquement la valeur d'une donn�e membre

Pour obtenir dynamiquement la valeur d'une donn�e membre en utilisant le moteur d'introspection de la biblioth�que QxOrm, il est n�cessaire de passer par la classe : qx::IxDataMember. La classe qx::IxDataMember fournit plusieurs m�thodes pour obtenir la valeur d'une donn�e membre (chacune prenant en param�tre un pointeur g�n�rique de type void * correspondant � l'adresse de l'instance courante) : Par exemple : imaginons un pointeur g�n�rique de type void * vers une classe person. Nous pouvons obtenir la valeur de la propri�t� firstName de type QString de la fa�on suivante :

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


Valoriser dynamiquement une donn�e membre

De la m�me fa�on que pour obtenir la valeur d'une donn�e membre, la classe qx::IxDataMember permet de valoriser une donn�e membre (modifier sa valeur). La classe qx::IxDataMember fournit les 2 m�thodes suivantes (chacune prend en param�tre un pointeur de type void * correspondant � l'adresse de l'instance courante, ainsi que la nouvelle valeur � positionner) :
  • fromVariant() : valorise la donn�e membre en fonction du param�tre de type QVariant ;
  • setValue<T>() : valorise la donn�e membre avec un param�tre du type r�el T de la donn�e membre.
Par exemple : imaginons un pointeur g�n�rique de type void * vers une classe person. Nous pouvons modifier la valeur de la propri�t� firstName de type QString de la fa�on suivante :

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


Appeler dynamiquement une fonction

Tout comme les donn�es membre (propri�t�s), il est possible d'enregistrer des m�thodes membre (fonctions) dans le contexte QxOrm (support des m�thodes static et non static). Le moteur d'introspection de la biblioth�que QxOrm permet d'invoquer dynamiquement des m�thodes de classe. Toutes les fonctions enregistr�es dans le contexte QxOrm sont associ�es � une instance de la classe : qx::IxFunction. Pour enregistrer des m�thodes dans le contexte QxOrm, il faut utiliser : Par exemple : on souhaite enregistrer dans le contexte QxOrm plusieurs m�thodes d'une classe person :

* Fichier person.h :
#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_

* Fichier person.cpp :
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");
}}


Une fois enregistr�es dans le contexte QxOrm, il est possible d'appeler dynamiquement ces fonctions avec les m�thodes qx::QxClassX::invoke() et 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);


Cr�er une instance C++ dynamiquement

Le moteur d'introspection de la biblioth�que QxOrm permet de cr�er dynamiquement des instances de classe (module QxFactory, mod�le de conception fabrique ou design pattern factory) avec les m�thodes suivantes :
  • qx::create(const QString & sKey) : cr�ation d'une instance de type sKey sous la forme qx::any (contenant un pointeur intelligent de type std::shared_ptr, alias de boost::shared_ptr par d�faut) ;
  • qx::create_nude_ptr<T>(const QString & sKey) : cr�ation d'une instance de type sKey sous la forme d'un pointeur nu de type T * (attention � lib�rer la m�moire de ce pointeur pour �viter les fuites m�moire) ;
  • qx::create_void_ptr(const QString & sKey) : cr�ation d'une instance de type sKey sous la forme d'un pointeur nu de type void * (attention � lib�rer la m�moire de ce pointeur pour �viter les fuites m�moire).
Par exemple : le module QxService de la biblioth�que QxOrm utilise ce m�canisme pour cr�er dynamiquement les instances de classe de service pour ex�cuter les routines c�t� serveur :

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


Parcourir la liste des classes/propri�t�s enregistr�es dans le contexte QxOrm

Voici un exemple d'utilisation du moteur d'introspection de la biblioth�que QxOrm : comment lister toutes les classes, propri�t�s et m�thodes enregistr�es dans le contexte QxOrm ?

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;
}

Si on utilise la m�thode qx::QxClassX::dumpAllClasses() avec le tutoriel qxBlog, voici le r�sultat obtenu :

[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 : transf�rer la couche de donn�es persistante sur le r�seau (module QxService)

Le module QxService de la biblioth�que QxOrm permet de cr�er rapidement un serveur d'applications C++ performant (notion de services avec demande du client et r�ponse du serveur). Un tutoriel est disponible sur le site QxOrm afin de pr�senter un exemple d'utilisation du module QxService. Le module QxService est bas� sur le moteur d'introspection ainsi que le moteur de s�rialisation de la biblioth�que QxOrm afin de transf�rer la couche de donn�es persistante sur le r�seau et ex�cuter automatiquement les routines c�t� serveur.

Remarque : pour activer le module QxService, il faut d�finir l'option de compilation _QX_ENABLE_QT_NETWORK dans le fichier de configuration QxOrm.pri (ou QxOrm.cmake). Cette option de compilation ajoute une d�pendance au binaire QtNetwork fourni avec la biblioth�que Qt.

Autre remarque : l'application QxEntityEditor est livr�e avec le plugin QxEECppServicesExport : ce plugin g�n�re automatiquement le code source n�cessaire pour transf�rer l'ensemble des entit�s d'un projet sur le r�seau. Une liste de m�thodes client/serveur est g�n�r�e automatiquement :
  • count() : requ�te client/serveur pour compter le nombre d'�l�ments (avec possibilit� d'utiliser un filtre SQL) ;
  • fetchById() : requ�te client/serveur pour alimenter les propri�t�s d'une entit� en fonction de son identifiant ;
  • fetchAll() : requ�te client/serveur pour alimenter les propri�t�s de toutes les entit�s d'une table ;
  • fetchByQuery() : requ�te client/serveur pour alimenter les propri�t�s des entit�s filtr�es par une requ�te SQL ;
  • insert() : requ�te client/serveur pour ins�rer les donn�es d'une entit� ;
  • update() : requ�te client/serveur pour mettre � jour les donn�es d'une entit� ;
  • save() : requ�te client/serveur pour sauvegarder les donn�es d'une entit� (insertion ou mise � jour) ;
  • deleteById() : requ�te client/serveur pour supprimer une entit� en fonction de son identifiant ;
  • deleteAll() : requ�te client/serveur pour supprimer tous les �l�ments de la table mapp�e � une entit� ;
  • deleteByQuery() : requ�te client/serveur pour supprimer tous les �l�ments en fonction d'une requ�te SQL ;
  • destroyById() : requ�te client/serveur pour supprimer une entit� en fonction de son identifiant (avec prise en compte de la suppression logique) ;
  • destroyAll() : requ�te client/serveur pour supprimer tous les �l�ments de la table mapp�e � une entit� (avec prise en compte de la suppression logique) ;
  • destroyByQuery() : requ�te client/serveur pour supprimer tous les �l�ments en fonction d'une requ�te SQL (avec prise en compte de la suppression logique) ;
  • executeQuery() : requ�te client/serveur pour ex�cuter une requ�te SQL personnalis�e ou proc�dure stock�e ;
  • exist() : requ�te client/serveur pour tester l'existence d'une entit� en fonction de son identifiant ;
  • isValid() : requ�te client/serveur pour tester la validit� d'une entit� (module QxValidator).
Il est possible d'ajouter de nouveaux services ou de personnaliser les services g�n�r�s automatiquement par l'application QxEntityEditor.

L'objectif de ce chapite est de pr�senter les concepts � mettre en oeuvre pour utiliser le module QxService :

Param�tres d'entr�e/sortie d'un service (requ�te/r�ponse)

Chaque fonction publi�e par un service dispose de param�tres d'entr�e (demande du client) et de param�tres de sortie (r�ponse du serveur). Ces param�tres d'entr�e/sortie doivent h�riter de l'interface qx::service::IxParameter et doivent �tre enregistr�es dans le contexte QxOrm (par la fonction void qx::register_class<T>).

Par exemple : voici un exemple de param�tres d'entr�e/sortie g�n�r�s automatiquement par l'application QxEntityEditor bas� sur la classe blog du tutoriel qxBlog :

* Fichier blog.services.gen.h :
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)

* Fichier blog.services.gen.cpp :
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


Remarque : comme on peut le constater sur l'exemple ci-dessus, les param�tres d'entr�e/sortie peuvent contenir des types complexes (des collections, des pointeurs, etc...). Il est donc possible et tr�s simple de transf�rer des structures complexes sur le r�seau avec le module QxService.

D�finir les fonctions publi�es par un service

Chaque service enregistr� dans le module QxService publie une liste de fonctions accessibles c�t� client (requ�tes client/serveur). Les services doivent h�riter de la classe de base qx::service::QxService<INPUT, OUTPUT> (les param�tres template INPUT et OUTPUT correspondant aux param�tres d'entr�e/sortie) et doivent �tre enregistr�s dans le contexte QxOrm (par la fonction void qx::register_class<T>).

Par exemple : voici un exemple de service g�n�r� automatiquement par l'application QxEntityEditor bas� sur la classe blog du tutoriel qxBlog :

* Fichier blog.services.gen.h :
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)

* Fichier blog.services.gen.cpp :
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...


Remarque : une fois d�finies dans le contexte QxOrm, le client peut appeler les fonctions publi�es par le service : les routines c�t� serveur sont alors ex�cut�es automatiquement. La s�rialisation des donn�es ainsi que la gestion de la couche r�seau pour le transfert des donn�es sont g�r�es de mani�re transparente par le module QxService.

Liste des options disponibles c�t� serveur

Le serveur d'application C++ bas� sur le module QxService dispose de plusieurs param�tres accessibles par la classe singleton qx::service::QxConnect :
  • setPort() : port d'�coute pour recevoir les requ�tes du client et envoyer les r�ponses du serveur ;
  • setThreadCount() : nombre de threads disponibles c�t� serveur pour traiter les demandes du client ;
  • setSerializationType() : type de s�rialisation utilis� pour envoyer les r�ponses du serveur ;
  • setCompressData() : permet de d�finir si les donn�es renvoy�es par le serveur sont compress�es ou non ;
  • setEncryptData() : permet de d�finir si les donn�es renvoy�es par le serveur sont crypt�es ou non (avec possibilit� de renseigner une cl� de cryptage).

Param�trage de la connexion c�t� client

La couche cliente bas�e sur le module QxService dispose de plusieurs param�tres accessibles par la classe singleton qx::service::QxConnect :
  • setIp() : adresse IP du serveur d'application C++ ;
  • setPort() : port utilis� par le serveur d'application C++ ;
  • setSerializationType() : type de s�rialisation utilis� par la couche cliente pour envoyer les requ�tes du client au serveur ;
  • setCompressData() : permet de d�finir si les donn�es envoy�es au serveur sont compress�es ou non ;
  • setEncryptData() : permet de d�finir si les donn�es envoy�es au serveur sont crypt�es ou non (avec possibilit� de renseigner une cl� de cryptage).

Gestion de l'authentification dans un service

Il est classique d'impl�menter un contr�le au niveau du serveur pour v�rifier l'utilisateur connect� � la couche cliente. L'interface qx::service::IxService (classe de base de tous les services enregistr�s par le module QxService) fournit des m�thodes virtuelles qui peuvent �tre surcharg�es pour g�rer cette probl�matique :
  • onBeforeProcess() : m�thode virtuelle appel�e syst�matiquement avant ex�cution de la routine serveur ;
  • onAfterProcess() : m�thode virtuelle appel�e syst�matiquement apr�s ex�cution de la routine serveur.

Par exemple : voici une classe de base nomm�e ParameterAuthentication qui peut �tre utilis�e par tous les param�tres d'entr�e/sortie, cette classe fournit 3 propri�t�s login, password et token :

* Fichier ParameterAuthentication.h :
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)

* Fichier ParameterAuthentication.cpp :
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


Maintenant que l'on dispose d'une classe de base pour nos param�tres (ParameterAuthentication), nous allons cr�er une classe de base utilis�e par tous nos services nomm�e ServiceAuthentication<INPUT, OUTPUT>. Cette classe de base des services va surcharger la m�thode virtuelle onBeforeProcess() afin de g�rer l'authentification avant ex�cution de la routine serveur :

* Fichier ServiceAuthentication.h :
#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 !");
   }
 
};


A pr�sent, nous disposons des classes de base ParameterAuthentication et ServiceAuthentication<INPUT, OUTPUT> : toutes les classes de param�tres et toutes les classes de services doivent h�riter de ces classes de base pour g�rer automatiquement l'authentification, et retourner une erreur au client si les param�tres de l'utilisateur ne sont pas valides.

Remarque : de la m�me fa�on que pour g�rer l'authentification, il est possible de mettre en place des logs automatiques en surchargeant les m�thodes virtuelles onBeforeProcess() et onAfterProcess().

Requ�tes client/serveur asynchrones

Par d�faut, les requ�tes client/serveur sont synchrones : ce qui signifie que la couche cliente attend la r�ponse du serveur pour continuer son ex�cution. Dans une interface graphique utilisateur (GUI), une requ�te client/serveur bloque l'application (freeze) si elle est ex�cut�e dans le thread principal : si le serveur met du temps pour renvoyer sa r�ponse, l'utilisateur peut alors penser qu'il s'agit d'un crash de l'application. La module QxService propose une solution simple pour effectuer des requ�tes asynchrones (qui ne bloquent donc pas l'interface graphique de l'utilisateur) gr�ce � la classe qx::service::QxClientAsync.

La classe qx::service::QxClientAsync utilise le moteur d'introspection de la biblioth�que QxOrm ainsi que le m�canisme SIGNAL-SLOT de Qt. Elle prend en param�tre :
  • une instance de service ;
  • les param�tres d'entr�e/sortie du service ;
  • le nom de la routine serveur � ex�cuter (sous forme de chaine de caract�res) ;
  • une fonction � appeler une fois que la transaction est termin�e (connexion � l'�v�nement signal finished()).

Voici l'exemple issu du tutoriel qxClientServer qui ex�cute une routine serveur de mani�re asynchrone :

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


Remarque : l'exemple ci-dessus montre comment effectuer une requ�te asynchrone avec les actions suivantes :
  • cr�ation d'une instance d'un service (de type server_infos_ptr pour cet exemple) ;
  • cr�ation d'une instance de type qx::service::QxClientAsync ;
  • connexion � l'�v�nement finished (pour indiquer qu'une r�ponse du serveur vient d'arriver) ;
  • passage de l'instance du service et de la m�thode � appeler (sous forme de chaine de caract�res) � l'objet qx::service::QxClientAsync ;
  • d�marrage de la transaction avec l'appel de la m�thode start().

Moteur mod�le/vue (module QxModelView)

Le module QxModelView permet d'utiliser le moteur model/view de Qt avec toutes les classes enregistr�es dans le contexte QxOrm :
  • QML : toute propri�t� enregistr�e dans le contexte QxOrm est accessible en QML : le module QxModelView permet ainsi de faciliter l'int�raction entre QML et les bases de donn�es ;
  • Qt widgets : utilisation de QTableView ou QListView par exemple pour afficher/modifier le contenu d'une table de la base de donn�es.
L'interface qx::IxModel propose une base commune pour tous les mod�les li�s aux classes persistantes d�clar�es dans le contexte QxOrm. Les m�thodes de cette classe pr�fix�es par 'qx' appellent les fonctions du namespace qx::dao et communiquent donc directement avec la base de donn�es. L'interface qx::IxModel fournit �galement des m�thodes d�finies Q_INVOKABLE et sont donc accessibles directement en QML :
  • qxCount_() : compte le nombre d'�l�ments dans la table de la base de donn�es associ�e au mod�le (avec possibilit� d'indiquer un filtre SQL) ;
  • qxFetchById_() : alimente le mod�le en fonction de l'identifiant pass� en param�tre ;
  • qxFetchAll_() : alimente le mod�le avec tous les �l�ments contenus dans la table de la base de donn�es associ�e au mod�le ;
  • qxFetchByQuery_() : alimente le mod�le avec les �l�ments de la table de la base de donn�es associ�e au mod�le en fonction d'une requ�te SQL ;
  • qxFetchRow_() : alimente (met � jour) une ligne du mod�le (chaque ligne du mod�le dispose de son propre identifiant de base de donn�es) ;
  • qxInsert_() : ins�re l'int�gralit� du mod�le en base de donn�es ;
  • qxInsertRow_() : ins�re une ligne du mod�le en base de donn�es ;
  • qxUpdate_() : met � jour l'int�gralit� du mod�le en base de donn�es ;
  • qxUpdateRow_() : met � jour une ligne du mod�le en base de donn�es ;
  • qxSave_() : sauvegarde l'int�gralit� du mod�le en base de donn�es (insertion ou mise � jour) ;
  • qxSaveRow_() : sauvegarde une ligne du mod�le en base de donn�es (insertion ou mise � jour) ;
  • qxDeleteById_() : supprime un �l�ment de la base de donn�es en fonction de l'identifiant pass� en param�tre ;
  • qxDeleteAll_() : supprime tous les �l�ments de la table de la base de donn�es associ�e au mod�le ;
  • qxDeleteByQuery_() : supprime les �l�ments de la table de la base de donn�es associ�e au mod�le en fonction d'une requ�te SQL ;
  • qxDeleteRow_() : supprime une ligne du mod�le de la base de donn�es (chaque ligne du mod�le dispose de son propre identifiant de base de donn�es) ;
  • qxDestroyById_() : supprime un �l�ment de la base de donn�es en fonction de l'identifiant pass� en param�tre (avec prise en compte de la suppression logique) ;
  • qxDestroyAll_() : supprime tous les �l�ments de la table de la base de donn�es associ�e au mod�le (avec prise en compte de la suppression logique) ;
  • qxDestroyByQuery_() : supprime les �l�ments de la table de la base de donn�es associ�e au mod�le en fonction d'une requ�te SQL (avec prise en compte de la suppression logique) ;
  • qxDestroyRow_() : supprime une ligne du mod�le de la base de donn�es (chaque ligne du mod�le dispose de son propre identifiant de base de donn�es), avec prise en compte de la suppression logique ;
  • qxExecuteQuery_() : alimente le mod�le en fonction d'une requ�te SQL personnalis�e ou proc�dure stock�e ;
  • qxExist_() : teste l'existence d'un �l�ment en fonction de l'identifiant pass� en param�tre ;
  • qxValidate_() : teste la validit� de l'int�gralit� du mod�le (module QxValidator) ;
  • qxValidateRow_() : teste la validit� d'une ligne du mod�le (module QxValidator).

Remarque : le projet de test qxBlogModelView pr�sent dans le dossier ./test/ du package QxOrm montre comment cr�er rapidement un mod�le et l'associer au moteur model/view de Qt (d'abord dans un widget Qt, puis dans une vue QML).

D�finir un mod�le "simple" (sans relation)

Toute classe enregistr�e dans le contexte QxOrm peut �tre utilis�e en tant que mod�le afin d'alimenter des vues. La classe de base qx::IxModel des mod�les QxOrm h�rite de la classe Qt QAbstractItemModel : les mod�les QxOrm sont donc enti�rement compatibles avec le moteur model/view de Qt.

Une seule ligne de code est suffisante pour instancier un mod�le QxOrm :

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

Remarque : le mod�le cr�� avec cette ligne de code expose automatiquement toutes les propri�t�s enregistr�es dans le contexte QxOrm au moteur model/view.

Mod�les avec relations (notion de mod�les imbriqu�s)

Adapter les relations entre classe (1-n, n-1 ou n-n) au moteur model/view de Qt est complexe : la solution propos�e par la biblioth�que QxOrm est l'utilisation de mod�les imbriqu�s. Pour plus de d�tails sur la notion de mod�les imbriqu�s, un tutoriel est disponible sur le site developpez.com.

Pour utiliser les relations (1-n, n-1 ou n-n) avec le module QxModelView, il est important de comprendre qu'il peut y avoir une hi�rarchie entre mod�les (un mod�le parent peut avoir plusieurs mod�les enfants associ�s, c'est la notion de mod�les imbriqu�s).

Afin de pouvoir travailler avec des relations (mod�les imbriqu�s), il est n�cessaire de cr�er des classes mod�les qui h�ritent de : qx::QxModel<T>. Ainsi, toutes les propri�t�s simples (non relation) sont automatiquement expos�es aux vues (gr�ce � la classe de base), il reste � �crire uniquement les accesseurs pour acc�der aux relations. L'application QxEntityEditor est livr�e avec le plugin QxEECppModelViewExport : ce plugin g�n�re automatiquement le code source pour pouvoir travailler avec des mod�les imbriqu�s.

Voici un exemple de code g�n�r� par l'application QxEntityEditor afin de cr�er un mod�le bas� sur la classe blog (voir le tutoriel qxBlog pour plus de d�tails). La classe blog dispose de 3 relations : author (n-1), list_of_comment (1-n) et list_of_category (n-n) :

* Fichier blog.model_view.gen.h :
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

* Fichier blog.model_view.gen.cpp :
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


Remarque : comme on peut le constater sur l'exemple ci-dessus, le code source � �crire pour travailler avec des mod�les imbriqu�s est verbeux. Afin de travailler avec les relations, il est donc fortement recommand� d'utiliser l'application QxEntityEditor afin de g�n�rer le code source automatiquement.

Int�raction avec les vues QML

Voici un exemple en QML (en Qt5, le module QxModelView �tant �galement compatible avec Qt4) qui utilise la table 'author' d�finie dans le tutoriel qxBlog (le code source de cet exemple QML est disponible dans le projet de test qxBlogModelView pr�sent dans le package QxOrm) :

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

Et voici le contenu du fichier 'main.qml' :

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

Ce qui donne le r�sultat suivant � l'ex�cution :

qx_model_view_02

Remarque : comme on peut le constater dans le fichier 'main.qml', les propri�t�s 'author_id' et 'name' du mod�le 'author' (variable myModel) sont accessibles automatiquement en lecture/�criture (car elles ont �t� enregistr�es dans le contexte QxOrm).
De plus, l'interface qx::IxModel propose une liste de m�thodes accessibles en QML (utilisation de Q_INVOKABLE) pour communiquer directement avec la base de donn�es : ainsi, le bouton 'Save' de l'�cran ci-dessus enregistre le mod�le en base de donn�es depuis QML.

Autre remarque : un plugin de l'application QxEntityEditor permet de g�n�rer automatiquement le code des mod�les pour la gestion des relations. Il est ainsi possible de travailler avec des mod�les imbriqu�s (pour plus de d�tails sur la notion de mod�les imbriqu�s, rendez-vous sur ce tutoriel du site developpez.com).

Int�raction avec les vues QtWidget

Voici un exemple de cr�ation d'un mod�le pour afficher/modifier les donn�es de la table 'author' (voir le tutoriel qxBlog pour la d�finition de la classe 'author') dans un QTableView (le code source de cet exemple est disponible dans le projet de test qxBlogModelView pr�sent dans le package QxOrm) :

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

Ce qui donne le r�sultat suivant � l'ex�cution :

qx_model_view_01

Remarque : Qt propose par d�faut plusieurs vues QtWidget qui peuvent �tre mapp�es sur un mod�le, par exemple : QListView, QTableView, QTreeView. Il est �galement possible d'utiliser la classe QDataWidgetMapper pour cr�er ses propres formulaires bas�s sur des mod�les (un tutoriel est disponible sur le site developpez.com).

Connexion d'un mod�le au module QxService

Le module QxModelView fournit la classe template : qx::QxModelService<T, S> (qui h�rite de : qx::QxModel<T> >> qx::IxModel >> QAbstractItemModel). Cette classe dispose de 2 param�tres template :
  • T : classe enregistr�e dans le contexte QxOrm dont toutes les propri�t�s sont expos�es au moteur model/view de Qt ;
  • S : classe de services du module QxService pour acc�der/enregistrer les donn�es du mod�le (requ�tes client/serveur).
Les donn�es propos�es par le mod�le sont ainsi issues de requ�tes client/serveur gr�ce au module QxService (elles ne proviennent pas directement de la base de donn�es). La classe de services S doit proposer les m�thodes suivantes :
  • count() : requ�te client/serveur pour compter le nombre d'�l�ments (avec possibilit� d'utiliser un filtre SQL) ;
  • fetchById() : requ�te client/serveur pour alimenter les propri�t�s du mod�le en fonction de son identifiant ;
  • fetchAll() : requ�te client/serveur pour alimenter les propri�t�s du mod�le qui contiendra tous les �l�ments d'une table ;
  • fetchByQuery() : requ�te client/serveur pour alimenter les propri�t�s du mod�le qui contiendra les �l�ments filtr�s par une requ�te SQL ;
  • insert() : requ�te client/serveur pour ins�rer les donn�es du mod�le ;
  • update() : requ�te client/serveur pour mettre � jour les donn�es du mod�le ;
  • save() : requ�te client/serveur pour sauvegarder les donn�es du mod�le (insertion ou mise � jour) ;
  • deleteById() : requ�te client/serveur pour supprimer un mod�le en fonction de son identifiant ;
  • deleteAll() : requ�te client/serveur pour supprimer tous les �l�ments de la table mapp�e au mod�le ;
  • deleteByQuery() : requ�te client/serveur pour supprimer tous les �l�ments en fonction d'une requ�te SQL ;
  • destroyById() : requ�te client/serveur pour supprimer un mod�le en fonction de son identifiant (avec prise en compte de la suppression logique) ;
  • destroyAll() : requ�te client/serveur pour supprimer tous les �l�ments de la table mapp�e au mod�le (avec prise en compte de la suppression logique) ;
  • destroyByQuery() : requ�te client/serveur pour supprimer tous les �l�ments en fonction d'une requ�te SQL (avec prise en compte de la suppression logique) ;
  • executeQuery() : requ�te client/serveur pour ex�cuter une requ�te SQL personnalis�e ou proc�dure stock�e ;
  • exist() : requ�te client/serveur pour tester l'existence du mod�le en fonction de son identifiant ;
  • isValid() : requ�te client/serveur pour tester la validit� du mod�le (module QxValidator).

Remarque : l'application QxEntityEditor est livr�e avec les plugins QxEECppServicesExport et QxEECppModelViewExport : ces plugins g�n�rent automatiquement tout le code source n�cessaire pour travailler avec des mod�les qui utilisent le module QxService. Afin de travailler avec la classe qx::QxModelService<T, S>, il est donc fortement recommand� d'utiliser l'application QxEntityEditor afin de g�n�rer le code source automatiquement.

QxOrm et MongoDB (C++ ODM Object Document Mapper)

En plus des bases de donn�es relationnelles classiques (MySQL, PostgreSQL, SQLite, Oracle, Microsoft SQLServer, MariaDB, etc...), la biblioth�que QxOrm supporte �galement la base de donn�es MongoDB.

D�finition du site Wikipedia : MongoDB est un syst�me de gestion de base de donn�es orient�e documents, r�partissable sur un nombre quelconque d'ordinateurs et ne n�cessitant pas de sch�ma pr�d�fini des donn�es. Il fait partie de la mouvance NoSQL.

La base de donn�es MongoDB pr�sente de nombreux avantages par rapport � une base de donn�es relationnelle classique (liste non exhaustive) :
  • Aucun sch�ma � d�finir : il est inutile d'avoir � maintenir des tables et colonnes (donc fini les scripts complexes pour migrer la base de donn�es d'une version � une autre). Les Collections MongoDB peuvent contenir des Documents avec diff�rents champs, diff�rentes tailles, etc... Concernant QxOrm, �a signifie que vous pouvez faire �voluer vos classes C++ sans vous soucier d'un sch�ma DDL � maintenir d'une version � une autre (convient parfaitement dans un environnement de d�veloppement AGILE par exemple) ;
  • Les donn�es sont stock�es dans un format BSON (correspond � du JSON) : facile � lire m�me avec des structures de donn�es complexes ;
  • Moteur de requ�tes (JSON) tr�s puissant avec possibilit� de positionner des index sur n'importe qu'elle propri�t� d'un Document ;
  • La base de donn�es est gratuite, et propose un support pour les professionnels ;
  • Depuis la version 3.6 de MongoDB : le moteur de requ�tes permet de cr�er des jointures (entre Documents) de mani�re �quivalente � une base de donn�es relationnelle classique.

L'utilisation de la biblioth�que QxOrm avec MongoDB est tr�s proche des bases de donn�es relationnelles classiques. Toutes les fonctionnalit�s de la biblioth�que QxOrm sont support�es avec MongoDB : donc tout ce qui se trouve dans ce manuel utilisateur est disponible avec une base de donn�es MongoDB. Les principales diff�rences � prendre en compte sont :
Remarque : le package QxOrm fournit un projet de test nomm� qxBlogMongoDB (dans le dossier ./test/). Ce projet montre comment se connecter � une base de donn�es MongoDB avec la biblioth�que QxOrm.

Pr�-requis : driver libmongoc et libbson

Le module QtSql fourni par le framework Qt sur lequel est bas� la biblioth�que QxOrm ne propose pas de connecteur � une base de donn�es MongoDB. La biblioth�que QxOrm a donc besoin de 2 d�pendances suppl�mentaires pour se connecter � une base MongoDB :
Un guide d'installation est disponible pour installer ces 2 biblioth�ques sur votre environnement de d�veloppement.

Param�trage du fichier QxOrm.pri (ou QxOrm.cmake)

Une fois que les biblioth�ques libmongoc et libbson sont correctement install�es sur votre environnement de d�veloppement, il est n�cessaire de param�trer le fichier de configuration QxOrm.pri (ou QxOrm.cmake) en activant l'option de compilation _QX_ENABLE_MONGODB.

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


Remarque : une fois l'option de compilation _QX_ENABLE_MONGODB activ�e, il est possible de compiler et ex�cuter le projet de test qxBlogMongoDB du dossier ./test/ afin de valider l'environnement de d�veloppement.

Connexion � la base de donn�es MongoDB

Voici un exemple de param�trage pour se connecter � une base de donn�es MongoDB :

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


D�finition d'une classe persistante MongoDB (Collection) dans le contexte QxOrm (mapping)

D�clarer une classe persistante MongoDB dans le contexte QxOrm est �quivalent � d�clarer une classe persistante pour une base de donn�es relationnelle classique. Voici un exemple du projet de test qxBlogMongoDB :

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

Fichier 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
}}

Remarque : l'exemple ci-dessus montre comment d�finir :

Gestion des cl�s primaires ObjectId

Comme indiqu� en jaune dans l'exemple pr�c�dent, il est conseill� de d�finir en C++ une cl� primaire de type QString. Il n'y a pas de notion de cl� num�rique auto-incr�ment�e : MongoDB utilise un type ObjectId qui peut �tre mapp� en C++ avec QString et g�n�r� automatiquement (il est �galement possible de d�finir son propre type C++ pour mapper un ObjectId MongoDB).

Ins�rer une instance C++ (Document) dans la base de donn�es MongoDB (INSERT)

Voici un exemple d'insertion de document avec g�n�ration automatique de la cl� primaire (de type 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);


Voici un exemple d'insertion de document avec une cl� primaire personnalis�e :

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


Ins�rer une liste d'instances C++ (plusieurs Documents) dans la base de donn�es MongoDB (INSERT)

Voici un exemple d'insertion de plusieurs documents dans la base de donn�es MongoDB :

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


Remarque : QxOrm supporte plusieurs types C++ de listes / collections.

Mettre � jour une instance C++ (Document) dans la base de donn�es MongoDB (UPDATE)

Voici un exemple de mise � jour d'un document dans la base MongoDB :

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


Mettre � jour une liste d'instances C++ (plusieurs Documents) dans la base de donn�es MongoDB (UPDATE)

Voici un exemple de mise � jour de plusieurs documents dans la base MongoDB :

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


Remarque : QxOrm supporte plusieurs types C++ de listes / collections.

Supprimer une instance C++ (Document) de la base de donn�es MongoDB (DELETE)

Voici un exemple de suppression d'un document de la base MongoDB :

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


Supprimer une liste d'instances C++ (plusieurs Documents) de la base de donn�es MongoDB (DELETE)

Voici un exemple de suppression de plusieurs documents de la base MongoDB par identifiant (cl� primaire) :

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


Voici un exemple de suppression de plusieurs documents de la base MongoDB par requ�te JSON :

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


Pour supprimer tous les documents de la Collection author :

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


Remarque : QxOrm supporte plusieurs types C++ de listes / collections.

R�cup�rer une instance C++ (Document) de la base de donn�es MongoDB (FETCH)

Voici un exemple pour r�cup�rer (FETCH) un document de la base MongoDB par identifiant (cl� primaire) :

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


R�cup�rer une liste d'instances C++ (plusieurs Documents) de la base de donn�es MongoDB (FETCH)

Voici un exemple pour r�cup�rer (FETCH) plusieurs documents de la base MongoDB par identifiant (cl� primaire) :

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


Voici un exemple pour r�cup�rer (FETCH) plusieurs documents de la base MongoDB par requ�te JSON :

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


Voici un exemple pour r�cup�rer (FETCH) tous les documents de la Collection author de la base MongoDB :

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


Voici un exemple pour r�cup�rer (FETCH) tous les documents de la Collection author de la base MongoDB (en s�lectionnant les propri�t�s/colonnes � r�cup�rer) :

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


Remarque : QxOrm supporte plusieurs types C++ de listes / collections.

Requ�tes JSON

La principale diff�rence entre une base de donn�es relationnelle classique et MongoDB est la fa�on de requ�ter les donn�es : � la place du SQL, MongoDB propose un moteur de requ�te JSON.

Utilisation de la classe qx::QxSqlQuery (ou son alias qx_query)

La classe qx::QxSqlQuery (ou son alias qx_query) utilis�e pour construire du SQL classique est �galement compatible pour construire des requ�tes JSON MongoDB. Cette classe utilise la fonctionnalit� C++11 std::initializer_list afin d'�crire les requ�tes en C++ avec une syntaxe proche du JSON (il est �galement possible d'�crire la requ�te sous forme de cha�ne de caract�res). Par exemple :

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


Utiliser le moteur d'aggregation MongoDB

La base de donn�es MongoDB fournit �galement un puissant moteur d'aggregation qui �tend encore plus les possibilit�s pour requ�ter les donn�es. Voici comment utiliser ce moteur d'aggregation avec la classe qx::QxSqlQuery (ou son alias qx_query), le 1er param�tre du constructeur doit �tre �gal � 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);


Ajouter des propri�t�s � la requ�te de type : 'sort', 'limit', 'skip', etc...

Il est souvent n�cessaire de limiter les donn�es, ou bien de les trier par exemple. Pour effectuer ces op�rations, la base de donn�es MongoDB utilise la notion de projection. Voici un exemple d'utilisation avec la classe qx::QxSqlQuery (ou son alias qx_query), avec une QStringList en constructeur (ou bien 2�me param�tre 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);


Ex�cuter une requ�te personnalis�e

Il est possible d'ex�cuter une requ�te personnalis�e avec la fonction qx::dao::call_query(). Le r�sultat de la requ�te peut �tre facilement convertie en QVariantMap ou bien QList<QVariantMap> (si la requ�te retourne un curseur) afin de faciliter la lecture des r�sultats de la requ�te personnalis�e. Voici quelques exemples d'appels de requ�tes personnalis�es :

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


Moteur de relations (n�cessite une version MongoDB 3.6 ou +)

Le moteur de relations de la biblioth�que QxOrm est compatible avec la base de donn�es MongoDB (version 3.6 minimale). QxOrm est donc capable de r�cup�rer les donn�es d'un Document sur plusieurs Collections en une seule requ�te.

Voici un exemple pour r�cup�rer un Document et toutes ses relations sur 1 niveau de profondeur (parent > enfants) :

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


Voici un exemple pour r�cup�rer un Document et toutes ses relations sur 4 niveaux de profondeur (utilisation de la syntaxe *->*->*->*) :

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


Voici un exemple pour r�cup�rer un Document en s�lectionnant les relations et propri�t�s � alimenter (utilisation de la syntaxe { <col_1>, <col_2>, etc... }) :

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


Voici un exemple pour r�cup�rer un Document en s�lectionnant les relations et les propri�t�s � ne pas alimenter (utilisation de la syntaxe -{ <col_1>, <col_2>, etc... }) :

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


Relations : Embedded vs Referenced

Un des points forts de la base de donn�es MongoDB est de pouvoir stocker des structures complexes de donn�es (on n'est pas limit� � une structure en tableau table/colonne des bases de donn�es relationnelles classiques). Un Document MongoDB peut donc contenir un objet et plusieurs sous-objets (notion de hi�rarchie dans la structure du Document). Inclure un sous-objet dans un m�me Document pr�sente des avantages (aucune jointure par exemple, donc plus rapide � r�cup�rer) et inconv�nients (un m�me objet peut �tre dupliqu� plusieurs fois dans la base). Il est donc important de r�fl�chir sur la strat�gie � adopter pour stocker les donn�es.

La biblioth�que QxOrm supporte les 2 fa�ons de proc�der :
  • Embedded relation : le sous-objet est inclu dans le Document ;
  • Referenced relation : cr�� une jointure comme dans une base de donn�es relationnelle classique.

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


Cr�ation automatique des index

La biblioth�que QxOrm fournit une m�thode pour g�n�rer automatiquement les index (cette fonction peut �tre appel�e en d�but de programme, par exemple dans le main) :
  • les index li�s aux relations entre les Collections (pour optimiser les jointures) ;
  • les index d�finis par la m�thode qx::IxDataMember::setIndex() (dans la fonction qx::register_class()).

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


Serveur web HTTP/HTTPS (module QxHttpServer)

La biblioth�que QxOrm fournit un serveur web compatible HTTP 1.1 autonome (aucune n�cessit� d'installer une application tierce comme Apache ou Nginx), performant (multi-thread) et simple d'utilisation : il s'agit du module QxHttpServer (bas� sur le module QxService).

Le module QxHttpServer supporte de nombreuses fonctionnalit�s : Combin� avec le module QxRestApi (qui propose une API JSON pour requ�ter les donn�es persistantes), le module QxHttpServer est particuli�rement adapt� pour d�velopper des applications web modernes. Par exemple, des applications web de type SPA (Single-Page Applications) avec les c�l�bres frameworks Javascript comme AngularJS, React, Meteor.js, etc...

Remarque : pour activer le module QxHttpServer, il faut d�finir l'option de compilation _QX_ENABLE_QT_NETWORK dans le fichier de configuration QxOrm.pri (ou QxOrm.cmake). Cette option de compilation ajoute une d�pendance au binaire QtNetwork fourni avec la biblioth�que Qt.

Autre remarque : le package QxOrm est livr� avec le projet de test qxBlogRestApi. Ce projet de test est une application web avec de nombreux exemples pour requ�ter les donn�es persistantes depuis une page web (HTML et Javascript).

Hello World !

Voici le code source d'une application web bas�e sur le module QxHttpServer (cette application renvoie Hello World ! au navigateur 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();
}


R�sultat : ouvrir un navigateur web (Chrome, Firefox, Safari, Internet Explorer, Opera, etc...) et aller � l'adresse http://localhost:9642/, l'�cran suivant doit apparaitre :

QxHttpServer Hello World !

Param�trage du serveur web HTTP

Les param�tres du serveur web HTTP sont accessibles avec la classe singleton qx::service::QxConnect :
  • setPort() : port d'�coute du serveur web (un serveur web classique �coute sur le port 80 mais vous pouvez d�finir une autre valeur) ;
  • setThreadCount() : nombre de threads utilis�s par le serveur web pour traiter les requ�tes HTTP (nombre de connexions clientes simultan�es) ;
  • setMaxWait() : temps d'attente maximum en milli-secondes (par exemple lecture ou �criture sur la socket), la valeur -1 signifie attendre ind�finiment ;
  • setCompressData() : si le client HTTP supporte la compression GZIP, alors les r�ponses de type texte (fichier HTML / Javascript / CSS, flux JSON, etc...) seront compress�s au format GZIP ;
  • setKeepAlive() : la socket avec le client reste connect�e pendant le laps de temps d�fini par cette fonction (en milli-secondes), la valeur -1 signifie ne jamais se d�connecter ;
  • setSessionTimeOut() : indique le temps d'attente (en milli-secondes) avant de d�truire une session (stockage par client c�t� serveur) non utilis�e.

Connexions s�curis�es SSL/TLS

La classe singleton qx::service::QxConnect fournit �galement des param�tres pour g�rer les connexions s�curis�es HTTPS (SSL et/ou TLS).
Voici un exemple de param�trage de connexions s�curis�es avec certificat serveur et autorit� de certification (voir le projet de test qxBlogRestApi pour tester ce code) :

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


Remarque : par d�faut, toutes les erreurs SSL sont ignor�es (erreurs souvent li�es � des probl�mes de certificats). Pour adapter votre serveur web � vos normes de s�curit�, vous pouvez utiliser les fonctions suivantes (classe singleton qx::service::QxConnect) :

Routage des URL (d�finir les endpoints / dispatcher)

Le module QxHttpServer fournit un moteur de routage des URL (dispatcher) pour d�finir les fonctions (ou lambda) � ex�cuter en fonction des param�tres de la requ�te HTTP (m�thode HTTP GET, POST, DELETE, etc... + URL demand�e).
Les fonctions (ou lambda) doivent avoir cette signature : void myRequestHandler(qx::QxHttpRequest & request, qx::QxHttpResponse & response);

La classe qx::QxHttpServer (ou son alias qx_http_server) dispose des m�thodes suivantes :
  • setCustomRequestHandler() : d�fini une fonction (ou lambda) ex�cut�e si aucune autre fonction n'a �t� trouv�e par le dispatcher ;
  • dispatch() : le 1er param�tre correspond � la m�thode HTTP (GET, POST, DELETE, etc...), le 2�me param�tre correspond � l'URL demand�e (ou son pattern), le 3�me param�tre correpond � la fonction (ou lambda) � ex�cuter ;
  • beforeDispatching() : fonction (ou lambda) ex�cut�e avant de traiter la requ�te HTTP (peut �tre utile par exemple pour tracer des logs, ou bien mettre en place un syst�me d'authentification) ;
  • afterDispatching() : fonction (ou lambda) ex�cut�e apr�s le traitement de la requ�te HTTP (peut �tre utile par exemple pour tracer des logs) ;
  • clearDispatcher() : n�ttoie toutes les r�gles de routage (seule la fonction ou lambda d�finie par setCustomRequestHandler() sera ex�cut�e).
Remarque : le dispatcher est thread-safe, vous pouvez donc red�finir les r�gles de routage des URL m�me si le serveur web est en cours d'ex�cution.

Autre remarque : chaque fonction (ou lambda) est ex�cut�e dans son propre thread . Le serveur web HTTP fourni par la biblioth�que QxOrm peut donc g�rer plusieurs requ�tes HTTP simultan�ment.

Exemple n�1 : cette r�gle de routage intercepte toutes les requ�tes de type GET dont l'URL commence par /files/, et renvoie comme r�ponse le contenu d'un fichier statique stock� sur le serveur (QDir::currentPath() indique le r�pertoire parent du stockage des fichiers statiques, et 5000 correspond � la taille pour l'envoi des fichiers par bloc / chunked response, ce dernier param�tre �tant optionnel) :

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


Exemple n�2 : cette r�gle de routage intercepte toutes les requ�tes de type POST dont l'URL est /qx, et utilise le module QxRestApi (qui propose une API JSON pour requ�ter les donn�es persistantes). Ces 2 exemples (avec l'exemple n�1 qui renvoie des fichiers statiques) sont suffisants pour d�marrer une application web de type SPA (Single-Page Applications) avec les c�l�bres frameworks Javascript comme AngularJS, React, Meteor.js, etc...

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


Exemple n�3 : cette r�gle de routage intercepte toutes les requ�tes de type GET dont l'URL est /test_big_json, et construit une r�ponse de type JSON contenant un tableau de 10000 �l�ments :

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


Remarque : l'ordre dans lequel est appel� les diff�rentes fonctions dispatch() est important. Le 1er �l�ment trouv� par le dispatcher correspondant � l'URL demand�e est ex�cut� (et ignore les �l�ments suivants). Il est donc indispensable de d�finir en 1er les URL les plus sp�cifiques, jusqu'aux URL les plus g�n�riques (par exemple, le pattern correspondant � toutes les URL est /*).

Routage dynamique des URL

Le dispatcher du module QxHttpServer supporte �galement la notion de routage dynamique des URL.
Il est possible de d�finir des variables au niveau de l'URL � router avec la syntaxe : <var_name:var_type> (var_type �tant optionnel, et peut prendre comme valeur : int, long, float, double, string).

Le routage dynamique est particuli�rement utile pour mettre en place une API REST.
Par exemple, le pattern /blog/<blog_id:int> associ� � la m�thode HTTP GET peut �tre utilis� pour r�cup�rer les donn�es d'un blog en fonction de son identifiant de type num�rique (fetch_by_id).

Il faut voir l'URL comme une liste de segments dont le s�parateur est le caract�re /.
Le dispatcher v�rifie que chaque segment de l'URL demand�e correspond au pattern utilis� afin de valider une fonction (ou lambda) � ex�cuter.
Pour r�cup�rer les valeurs des param�tres de l'URL, il faut utiliser : request.dispatchParams().value("var_name") (retourne un QVariant).

Exemple : la r�gle de routage suivante intercepte toutes les requ�tes de type GET dont l'URL commence /params/, suivi par un segment qui sera associ� � la variable var1, suivi par un segment de type num�rique associ� � la variable var2. Elle construit une r�ponse qui renvoie la valeur des 2 param�tres var1 et var2. Si le navigateur web appelle l'URL /params/abc/123/ alors la fonction (ou lambda) sera ex�cut�e, par contre si le navigateur web appelle l'URL /params/abc/def/ alors la fonction (ou lambda) ne sera pas ex�cut�e (car def n'est pas num�rique) et cherchera un autre �l�ment du dispatcher :

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";
});


Remarque : il est �galement possible de d�finir une expression r�guli�re pour router les URL avec la syntaxe : <var_name:{my_reg_exp}> .

R�cup�rer les param�tres de la requ�te HTTP

La classe qx::QxHttpRequest (ou son alias qx_http_request) contient tous les param�tres d'appel de la requ�te HTTP :
  • QUrl & url() : URL demand�e par le navigateur web client ;
  • QString & command() : m�thode HTTP utilis�e par le navigateur web client (GET, POST, PUT, DELETE, etc...) ;
  • QString & version() : version HTTP fournie par le navigateur web client (en g�n�ral HTTP/1.1) ;
  • QByteArray & data() : contenu de la requ�te HTTP ;
  • QByteArray header(const QByteArray & key) : permet de r�cup�rer la valeur d'un en-t�te HTTP fourni par le navigateur web client (par exemple : request.header("Accept-Encoding")) ;
  • QxHttpCookie cookie(const QByteArray & name) : permet de r�cup�rer la valeur d'un cookie HTTP fourni par le navigateur web client ;
  • QString param(const QString & key) : permet de r�cup�rer la valeur d'un param�tre de la requ�te HTTP (fourni soit dans l'URL, soit dans le contenu si l'en-t�te HTTP 'content-type' est 'application/x-www-form-urlencoded') ;
  • QHash<QString, QVariant> & dispatchParams() : liste des param�tres dynamiques de l'URL calcul�s par le routage/dispatcher ;
  • QString & sourceAddress() : adresse IP du navigateur web client ;
  • long & sourcePort() : port utilis� par le navigateur web client ;
  • QString guid() : identifiant unique de la requ�te HTTP � usage interne uniquement (peut �tre utilis� pour tracer des logs par exemple).

G�n�rer la r�ponse HTTP

La classe qx::QxHttpResponse (ou son alias qx_http_response) permet de g�n�rer la r�ponse HTTP avec les m�thodes suivantes :
  • int & status() : code retour de la r�ponse HTTP (par d�faut 200) ;
  • QByteArray & data() : contenu de la r�ponse HTTP ;
  • QByteArray header(const QByteArray & key) : renseigne un en-t�te HTTP � envoyer au navigateur web client (par d�faut, les en-t�tes suivants sont cr��s : Server, Date, Content-Type et Connection) ;
  • QxHttpCookie cookie(const QByteArray & name) : renseigne un cookie HTTP � envoyer au navigateur web client ;
  • qx_bool writeChunked(const QByteArray & data) : permet d'envoyer le contenu de la r�ponse par bloc (chunked responses).

Sessions (stockage par client c�t� serveur)

Les sessions HTTP sont un m�canisme c�t� serveur web permettant de stocker des donn�es sp�cifiques � un client. Ces donn�es sont accessibles pendant un laps de temps pour toutes les requ�tes envoy�es par le client. Lors du 1er acc�s � une session pour un client, un cookie HTTP contenant un identifiant unique est g�n�r� et associ� automatiquement � la r�ponse HTTP. Par la suite, toutes les requ�tes HTTP envoy�es par le client contiendront automatiquement un cookie HTTP avec l'identifiant unique calcul� pr�c�demment. Lorsqu'une session n'est plus utilis�e pendant un certain laps de temps, alors elle est d�truite automatiquement.

La classe qx::QxHttpSession (ou son alias qx_http_session) repr�sente une session HTTP c�t� serveur.
Une session est accessible avec le singleton qx::QxHttpSessionManager :

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


Remarque : la classe qx::QxHttpSession contient une hash-map (QHash<QByteArray, QVariant>) pouvant stocker n'importe quelle valeur.

Autre remarque : la dur�e (en milli-secondes) pour supprimer une session non utilis�e est param�tr�e par la m�thode : qx::service::QxConnect::setSessionTimeOut().

Cookies

Les cookies HTTP sont un m�canisme d'�change de donn�es entre client HTTP et serveur HTTP.
Les cookies sont utilis�s par exemple pour :
  • g�rer les sessions HTTP ;
  • m�moriser l'information sur l'utilisateur d'un site, dans le but de lui montrer un contenu appropri� dans le futur. Par exemple, un serveur web peut envoyer un cookie contenant le dernier nom d'utilisateur utilis� pour se connecter � ce site web, afin que ce nom d'utilisateur puisse �tre pr�-rempli lors des prochaines visites ;
  • voir Wikipedia pour d'autres cas d'utilisation.

Les classes qx::QxHttpRequest et qx::QxHttpResponse disposent des m�thodes n�cessaires pour lire les cookies envoy�s par le navigateur web client ou bien g�n�rer un cookie dans la r�ponse HTTP. Par exemple :

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


Gestion des fichiers statiques

La classe qx::QxHttpServer (ou son alias qx_http_server) dispose d'une m�thode statique permettant d'envoyer au navigateur web client des fichiers stock�s sur le serveur (par exemple : fichiers HTML, Javascript, CSS, images PNG, JPEG, vid�os, etc...).

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

  • Le 3�me param�tre (dans l'exemple QDir::currentPath()) repr�sente le r�pertoire parent o� sont stock�s les fichiers statiques sur le serveur web ;
  • Le 4�me param�tre (dans l'exemple 5000) est optionnel et correspond � la taille pour l'envoi des fichiers par bloc (chunked response). Ce param�tre peut �tre utile pour envoyer des fichiers volumineux (streaming).

Encodage de transfert en bloc (chunked responses)

D�finition du site Wikipedia : Chunked transfer encoding (ou Encodage de transfert en bloc) est un m�canisme de transfert de donn�es de la version 1.1 du protocole Hypertext Transfer Protocol (HTTP), qui permet � un serveur ou � un client de commencer � transmettre des donn�es par blocs sans avoir � conna�tre � l'avance la taille totale des donn�es qui seront transmises. Dans le protocole HTTP, l'en-t�te "Content-Length" peut remplacer la directive "Chunked transfer encoding" d�crite ici. La taille en octets de chaque bloc est envoy�e, sous forme de texte en hexadecimal, juste avant le bloc lui-m�me afin que le serveur puisse dire au client quand il a fini de recevoir les donn�es de ce bloc. Le transfert total d'un fichier encod� par blocs se termine par un bloc final au contenu nul.

L'introduction de l'encodage de transfert en bloc du protocole HTTP 1.1 a fourni un certain nombre d'avantages :
  • Permettre � un serveur de maintenir une connexion HTTP persistante pour un contenu g�n�r� dynamiquement.
  • Permettre � l'exp�diteur d'envoyer des en-t�tes suppl�mentaires apr�s le corps du message. Sans l'encodage de transfert en bloc, l'exp�diteur devrait tamponner le contenu jusqu'� ce qu'il soit compl�t� afin de calculer une valeur et l'envoyer avant le contenu.

La classe qx::QxHttpResponse dispose de la m�thode qx_bool writeChunked(const QByteArray & data). Cette m�thode permet d'envoyer la r�ponse par bloc. Elle est utilis�e par exemple pour envoyer des fichiers statiques volumineux :

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


Remarque : le 1er appel de la m�thode response.writeChunked() d�clenche automatiquement l'envoi de tous les en-t�tes HTTP de la r�ponse. Il faut donc d�finir tous les en-t�tes de la r�ponse HTTP avant d'appeler response.writeChunked() pour la 1�re fois.

Requ�tes par les API JSON (module QxRestApi)

Le module QxRestApi propose une API JSON g�n�rique pour requ�ter les donn�es persistantes (op�rations CRUD, requ�tes complexes avec plusieurs niveaux de relations, possibilit� de d�finir un format de sortie JSON, appels dynamiques � des fonctions natives C++, validation d'instances, requ�tes personnalis�es � la base de donn�es).

Ce manuel utilisateur dispose d'un chapitre entier d�di� au module QxRestApi : il contient notamment de nombreux exemples d'utilisation. En combinant le module QxRestApi et le module QxHttpServer : vous avez tous les outils n�cessaires pour d�velopper des applications web modernes. Par exemple, des applications web de type SPA (Single-Page Applications) avec les c�l�bres frameworks Javascript comme AngularJS, React, Meteor.js, etc...

Remarque : le package QxOrm contient un projet de test qxBlogRestApi. Ce projet pr�sente la cr�ation d'un serveur web HTTP avec QxOrm, et �galement l'�criture de la partie cliente en HTML + Javascript (avec utilisation de jQuery).

Par exemple, voici la fonction Javascript utilis�e pour envoyer les requ�tes JSON (m�thode POST) depuis le client (navigateur web) vers le serveur web HTTP QxOrm (toutes les requ�tes sont envoy�es � la m�me adresse /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);
   });
}


C�t� serveur, la r�ception et le traitement de ces requ�tes est tr�s simple : la classe qx::QxHttpServer (ou son alias qx_http_server) dispose de la m�thode statique qx::QxHttpServer::buildResponseQxRestApi() :

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


Voici un exemple de requ�te JSON envoy�e par le navigateur web, elle r�cup�re la liste des tous les blogs de la base de donn�es (fetch_all) :

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


Voici la r�ponse JSON renvoy�e par le serveur web HTTP contenant la liste de 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

D�finition du site Wikipedia : le protocole WebSocket vise � d�velopper un canal de communication full-duplex sur un socket TCP pour les navigateurs et les serveurs web et permet :
  • la notification au client d'un changement d'�tat du serveur ;
  • l'envoi de donn�es en mode � pousser � (m�thode Push) du serveur vers le client (sans que ce dernier ait � effectuer une requ�te).

La biblioth�que QxOrm est bas�e sur le framework Qt qui dispose d�j� d'une impl�mentation WebSocket.
La mise en place d'un serveur web avec les WebSockets Qt est tr�s simple : il y a plusieurs exemples dans la documentation Qt.

Il est donc tout � fait possible d'impl�menter un serveur web avec :
  • un port d'�coute d�di� � toutes les connexions HTTP (utilisant le module QxHttpServer) ;
  • un autre port d'�coute d�di� � toutes les connexions WebSockets (utilisant le module QtWebSockets fourni par Qt).

Remarque : une connexion WebSocket �tant g�n�ralement cr��e par le code Javascript du navigateur web client, le fait d'avoir 2 ports ouverts sur le serveur web n'est pas un probl�me.

Performance (test� avec Apache Benchmark)

Voici les r�sultats d'un test de performance r�alis� avec les param�tres suivants :
  • Syst�me d'exploitation : Windows 2010 64bits ;
  • Processeur : Intel Core i7-6820HQ @ 2.70GHz (laptop) ;
  • Version Qt : 5.1.1 (en mode release) ;
  • Version QxOrm : 1.4.6 (compil� en mode release avec Visual Studio 2012, avec les param�tres par d�faut, aucune optimisation particuli�re) ;
  • Serveur web HTTP : projet de test qxBlogRestApi ;
  • Outil de test : Apache Benchmark ;
  • Simulation de 20000 requ�tes avec 50 clients connect�s simultan�ment : ab -n 20000 -c 50 -k http://localhost:9642/params/abc/123

Le r�sultat indique que le serveur web HTTP QxOrm peut g�rer plus de 12000 requ�tes par seconde :

QxHttpServer performance

Am�liorer les performances avec epoll dispatcher sous Linux

Sous Linux, il est possible d'ameliorer significativement les performances du serveur web HTTP en utilisant le m�canisme epoll pour g�rer les socket. Par d�faut, le framework Qt utilise un autre m�canisme (select) plus lent, mais donne la possibilit� de d�finir une autre gestion d'�v�nements. Plusieurs biblioth�ques existent, par exemple :
La classe qx::QxHttpServer (ou son alias qx_http_server) dispose de la m�thode suivante pour d�finir des �v�nements bas�s sur epoll (� appeler avant le d�marrage du serveur web) :

   httpServer.setEventDispatcher(new QEventDispatcherEpoll());   


API REST JSON (module QxRestApi)

Le module QxRestApi est une API JSON pour g�rer (de fa�on g�n�rique) la couche de donn�es persistantes (base de donn�es) ou appeler des fonctions natives C++ (enregistr�es dans le contexte QxOrm). Le module QxRestApi est bas� sur un m�canisme requ�te/r�ponse : envoi d'une requ�te au format JSON et r�ception d'une r�ponse au format JSON. Le module QxRestApi est particuli�rement adapt� pour d�velopper des services REST.

Le module QxRestApi supporte les fonctionnalit�s suivantes :
  • op�rations CRUD ;
  • requ�tes complexes avec plusieurs niveaux de relations ;
  • possibilit� de d�finir un format de sortie JSON ;
  • appels dynamiques � des fonctions natives C++ ;
  • validation d'instances ;
  • requ�tes personnalis�es � la base de donn�es ou proc�dures stock�es.

Principe de fonctionnement

Le module QxRestApi est tr�s simple d'utilisation : la classe qx::QxRestApi permet d'utiliser les API JSON avec une seule m�thode : processRequest().
Pr�requis : pour pouvoir utiliser le module QxRestApi, les classes enregistr�es dans le contexte QxOrm doivent impl�menter l'interface qx::IxPersistable.

La structure d'une requ�te JSON est g�n�rique et contient les �l�ments suivants :

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


La r�ponse JSON contient les �l�ments suivants :

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


Cas d'utilisation

Plusieurs langages de programmation supporte le JSON nativement (Javascript, PHP, Python, etc...). Le module QxRestApi permet ainsi une interop�rabilit� entre la biblioth�que QxOrm et d'autres applications utilisant d'autres technologies (autre que C++/Qt par exemple).

Le module QxRestApi peut �tre utilis� :

Projet de test qxBlogRestApi (QML et serveur web HTTP)

Le package QxOrm est livr� avec un projet de test nomm� qxBlogRestApi (dans le dossier ./test/qxBlogRestApi/).
Ce projet de test montre 2 cas d'utilisation du module QxRestApi :
  • La 1�re fen�tre correspond � une application QML qui utilise le moteur JS int�gr� � QML pour requ�ter les donn�es persistantes ou appeler des fonctions natives C++ :

    QxHttpServer performance


  • La 2�me fen�tre d�marre un serveur web HTTP bas� sur le module QxHttpServer, puis ouvre le navigateur web par d�faut � l'adresse correspondante (HTML + Javascript avec jQuery) :

    QxHttpServer performance

Ces 2 fen�tres sont d�velopp�es avec 2 technologies diff�rentes (QML versus HTML + Javascript), mais proposent exactement les m�mes fonctionnalit�s :
  • En haut � gauche de l'�cran : zone permettant d'�crire la requ�te JSON � envoyer au module QxRestApi ;
  • Juste en dessous de la requ�te JSON : un bouton permettant d'envoyer la requ�te JSON au module QxRestApi ;
  • En bas � gauche de l'�cran : une liste d'exemples de requ�tes JSON pr�tes � �tre ex�cut�es (un click dans cette liste alimente automatiquement la requ�te JSON � envoyer au module QxRestApi) ;
  • A droite de l'�cran : la r�ponse JSON fournie par le module QxRestApi apr�s traitement de la requ�te.

R�cup�ration de donn�es (fetch/count/exist)

Ce chapitre d�taille les diff�rentes m�thodes pour r�cup�rer les donn�es issues de la base de donn�es :
  • R�cup�rer tous les �l�ments d'une table et �ventuellement les relations associ�es (fetch_all) ;
  • R�cup�rer les donn�es d'un �l�ment d'une table en fonction de son identifiant unique (fetch_by_id) ;
  • R�cup�rer les �l�ments d'une table filtr�s par une requ�te (fetch_by_query) ;
  • Compter les �l�ments d'une table avec ou sans requ�te (count) ;
  • Tester l'existence d'un ou plusieurs �l�ments d'une table en fonction de l'identifiant (exist).

fetch_all

L'action fetch_all permet de r�cup�rer tous les �l�ments d'une table de la base de donn�es (et �ventuellement les relations associ�es sur plusieurs niveaux).

-- Exemple n�1 -- r�cup�rer tous les blogs (sous forme de liste) :

Requ�te JSON :
{
   "request_id": "5e988bac-c812-4cb1-b0d8-6a2c9dc4478b",
   "action": "fetch_all",
   "entity": "blog"
}
R�ponse JSON :
{
   "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"
}


-- Exemple n�2 -- r�cup�rer tous les blogs (sous forme de collection type hash-map avec cl�/valeur) :

Requ�te JSON :
{
   "request_id": "ad400135-19fd-40e0-8034-201be6a2ff7a",
   "action": "fetch_all",
   "entity": "blog",
   "data": [
      {
         "key": "",
         "value": ""
      }
   ]
}
R�ponse JSON :
{
   "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"
}


-- Exemple n�3 -- r�cup�rer tous les blogs et toutes les relations associ�es sur 2 niveaux :

Requ�te JSON :
{
   "request_id": "cf9ea2a8-3e41-438f-9a48-bbc8593d2b99",
   "action": "fetch_all",
   "entity": "blog",
   "relations": [
      "*->*"
   ]
}
R�ponse JSON :
{
   "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"
}


-- Exemple n�4 -- r�cup�rer tous les blogs et plusieurs relations associ�es en d�finissant un format de sortie (toutes les propri�t�s ne feront pas partie de la r�ponse JSON) :

Requ�te JSON :
{
   "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 -> *"
   ]
}
R�ponse JSON :
{
   "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

L'action fetch_by_id permet de r�cup�rer les donn�es d'un �l�ment d'une table en fonction de son identifiant unique.

-- Exemple n�1 -- r�cup�rer les donn�es du blog qui a pour identifiant unique 1 :

Requ�te JSON :
{
   "request_id": "4d6fbb9e-e088-482a-abfa-4e7ddee80569",
   "action": "fetch_by_id",
   "entity": "blog",
   "data": {
      "blog_id": 1
   }
}
R�ponse JSON :
{
   "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"
}


-- Exemple n�2 -- r�cup�re uniquement quelques donn�es du blog qui a pour identifiant unique 1 (les autres donn�es font partie du JSON mais avec une valeur vide ou null) :

Requ�te JSON :
{
   "request_id": "72c9b362-d194-410e-98ed-23797a34318e",
   "action": "fetch_by_id",
   "entity": "blog",
   "data": {
      "blog_id": 1
   },
   "columns": [
      "blog_text",
      "date_creation"
   ]
}
R�ponse JSON :
{
   "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"
}


-- Exemple n�3 -- r�cup�re une liste de blogs en fonction de leur identifiant :

Requ�te JSON :
{
   "request_id": "59c37f70-26ee-42e5-9177-b32c331adce1",
   "action": "fetch_by_id",
   "entity": "blog",
   "data": [
      {
         "blog_id": 1
      },
      {
         "blog_id": 2
      },
      {
         "blog_id": 3
      }
   ]
}
R�ponse JSON :
{
   "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"
}


-- Exemple n�4 -- r�cup�re une liste de blogs (avec quelques relations associ�es) en fonction de leur identifiant, et d�fini un format de sortie (toutes les propri�t�s ne feront pas partie de la r�ponse JSON) :

Requ�te JSON :
{
   "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 }"
   ]
}
R�ponse JSON :
{
   "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

L'action fetch_by_query permet de r�cup�rer les �l�ments d'une table filtr�s par une requ�te.

-- Exemple n�1 -- r�cup�re uniquement les �l�ments de la table author dont le sexe est de type female (female == enum dont la valeur est 1) :

Requ�te JSON :
{
   "request_id": "c178194c-a76f-4a77-af12-2b97fc7078e4",
   "action": "fetch_by_query",
   "entity": "author",
   "query": {
      "sql": "WHERE author.sex = :sex",
      "params": [
         {
            "key": ":sex",
            "value": 1
         }
      ]
   }
}
R�ponse JSON :
{
   "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"
}


-- Exemple n�2 -- r�cup�re uniquement les �l�ments de la table author (et toutes ses relations associ�es) dont le sexe est de type female :

Requ�te JSON :
{
   "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": [
      "*"
   ]
}
R�ponse JSON :
{
   "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"
}


-- Exemple n�3 -- r�cup�re uniquement les �l�ments de la table author (et toutes ses relations associ�es) dont le sexe est de type female, et d�fini un format de sortie (toutes les propri�t�s ne feront pas partie de la r�ponse JSON) :

Requ�te JSON :
{
   "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 }"
   ]
}
R�ponse JSON :
{
   "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

L'action count permet de compter les �l�ments d'une table avec ou sans requ�te (et avec ou sans relation).

-- Exemple n�1 -- compter le nombre de blogs dans la base de donn�es :

Requ�te JSON :
{
   "request_id": "1ef62fd7-d847-4d67-9fd0-0207af463aa4",
   "action": "count",
   "entity": "blog"
}
R�ponse JSON :
{
   "data": {
      "count": 4
   },
   "request_id": "1ef62fd7-d847-4d67-9fd0-0207af463aa4"
}


-- Exemple n�2 -- compter tous les author dont le sexe est de type female :

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


-- Exemple n�3 -- compter tous les blogs dont l'author est de type female :

Requ�te JSON :
{
   "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 }"
   ]
}
R�ponse JSON :
{
   "data": {
      "count": 4
   },
   "request_id": "6ef252f7-385c-465e-8304-b9afa9fea490"
}


exist

L'action exist permet de tester l'existence d'un ou plusieurs �l�ments d'une table en fonction de l'identifiant.

-- Exemple n�1 -- tester l'existence d'un blog dont l'identifiant unique a pour valeur 1 :

Requ�te JSON :
{
   "request_id": "e8db33db-b249-4349-93fe-ad12e208520e",
   "action": "exist",
   "entity": "blog",
   "data": {
      "blog_id": 1
   }
}
R�ponse JSON :
{
   "data": {
      "exist": true
   },
   "request_id": "e8db33db-b249-4349-93fe-ad12e208520e"
}


-- Exemple n�2 -- tester l'existence de plusieurs blogs :

Requ�te JSON :
{
   "request_id": "f2d6ca3f-36de-4920-8f4c-c04842603467",
   "action": "exist",
   "entity": "blog",
   "data": [
      {
         "blog_id": 1
      },
      {
         "blog_id": 999
      },
      {
         "blog_id": 3
      }
   ]
}
R�ponse JSON :
{
   "data": {
      "exist": false
   },
   "request_id": "f2d6ca3f-36de-4920-8f4c-c04842603467"
}


-- Exemple n�3 -- tester l'existence d'un author :

Requ�te JSON :
{
   "request_id": "2c7df172-8010-4816-b8e1-3edbb0b0b90e",
   "action": "exist",
   "entity": "author",
   "data": {
      "author_id": "author_id_2"
   }
}
R�ponse JSON :
{
   "data": {
      "exist": true
   },
   "request_id": "2c7df172-8010-4816-b8e1-3edbb0b0b90e"
}


Ajout de donn�es (insert)

L'action insert permet d'ins�rer un ou plusieurs �l�ments dans la base de donn�es. Les identifiants uniques g�n�r�s par la base de donn�es (par exemple identifiant auto-incr�ment�) sont fournis dans la r�ponse JSON.

-- Exemple n�1 -- ins�rer un blog dans la base de donn�es :

Requ�te JSON :
{
   "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"
   }
}
R�ponse JSON :
{
   "data": {
      "blog_id": 5
   },
   "request_id": "573e4940-607a-4037-8a09-11ec52deb21c"
}


-- Exemple n�2 -- ins�rer une liste de blogs dans la base de donn�es :

Requ�te JSON :
{
   "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"
      }
   ]
}
R�ponse JSON :
{
   "data": [
      {
         "blog_id": 6
      },
      {
         "blog_id": 7
      }
   ],
   "request_id": "6ade2d01-086c-45d6-971b-b65e8836475f"
}


-- Exemple n�3 -- ins�rer un author dans la base de donn�es :

Requ�te JSON :
{
   "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
   }
}
R�ponse JSON :
{
   "data": {
      "author_id": "author_id_from_rest_api"
   },
   "request_id": "0cffa916-99f4-4395-bccd-02918a4b3c57"
}

Remarque : l'identifiant unique de la table author doit �tre fourni par l'appelant (non auto-incr�ment�). Si on rejoue une 2�me fois la m�me requ�te, on obtient l'erreur suivante :
{
   "error": {
      "code": 19,
      "desc": "Unable to fetch row\ncolumn author_id is not unique"
   },
   "request_id": "0cffa916-99f4-4395-bccd-02918a4b3c57"
}


Mise � jour de donn�es (update)

L'action update permet une mise � jour de un ou plusieurs �l�ments dans la base de donn�es.

-- Exemple n�1 -- mise � jour du blog avec pour identifiant unique la valeur 1 :

Requ�te JSON :
{
   "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"
   }
}
R�ponse JSON :
{
   "data": {
      "blog_id": 1
   },
   "request_id": "4fa24a7f-a3d8-4bbf-85c1-c86df83dec0b"
}


-- Exemple n�2 -- mise � jour uniquement de certaines colonnes d'un blog :

Requ�te JSON :
{
   "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"
   ]
}
R�ponse JSON :
{
   "data": {
      "blog_id": 2
   },
   "request_id": "d0704db1-5c3a-48ad-b27e-14aa54ac0efb"
}


-- Exemple n�3 -- mise � jour de plusieurs author :

Requ�te JSON :
{
   "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
      }
   ]
}
R�ponse JSON :
{
   "data": [
      {
         "author_id": "author_id_from_rest_api"
      },
      {
         "author_id": "author_id_1"
      }
   ],
   "request_id": "26ec3a7b-cf2d-47f7-bab7-db303f15ee51"
}


Sauvegarde de donn�es (save)

L'action save permet d'ins�rer ou mettre � jour (insert ou update) un ou plusieurs �l�ments dans la base de donn�es. En cas d'insertion, les identifiants uniques g�n�r�s par la base de donn�es (par exemple identifiant auto-incr�ment�) sont fournis dans la r�ponse JSON.

La requ�te JSON dispose d'un param�tre optionnel nomm� save_mode qui peut prendre les valeurs suivantes :
  • check_insert_or_update : sauvegarde l'instance et les relations de fa�on r�cursive (sur plusieurs niveaux) en v�rifiant pour chaque relation s'il faut faire un insert ou update (m�thode pouvant �tre lente si beaucoup de relations � traiter) ;
  • insert_only : ins�re de fa�on r�cursive (sur plusieurs niveaux) l'instance et toutes les relations associ�es ;
  • update_only : met � jour de fa�on r�cursive (sur plusieurs niveaux) l'instance et toutes les relations associ�es.

-- Exemple n�1 -- sauvegarde (ins�re ou met � jour suivant l'identifant unique) un blog dans la base de donn�es :

Requ�te JSON :
{
   "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"
   }
}
R�ponse JSON :
{
   "data": {
      "blog_id": 1
   },
   "request_id": "ec3c71eb-5014-4b36-85a0-aeb7ae48a5e9"
}


-- Exemple n�2 -- sauvegarde (ins�re ou met � jour suivant l'identifant unique) une liste de blogs dans la base de donn�es :

Requ�te JSON :
{
   "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"
      }
   ]
}
R�ponse JSON :
{
   "data": [
      {
         "blog_id": 1
      },
      {
         "blog_id": 5
      }
   ],
   "request_id": "dc7c804e-f95a-4a9b-a4e3-547adcacf090"
}


-- Exemple n�3 -- sauvegarde (ins�re ou met � jour suivant l'identifant unique) un blog et toutes ses relations sur plusieurs niveaux (de fa�on r�cursive) :

Requ�te JSON :
{
   "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"
}
R�ponse JSON :
{
   "data": {
      "blog_id": 1
   },
   "request_id": "5b78e468-2fa3-4aeb-82ce-4d85408f5fa7"
}


-- Exemple n�4 -- ins�re (save_mode = insert_only) un blog et toutes ses relations sur plusieurs niveaux (de fa�on r�cursive) :

Requ�te JSON :
{
   "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"
}
R�ponse JSON :
{
   "data": {
      "blog_id": 7
   },
   "request_id": "ef147c62-74e0-4be2-a294-ffeb020d5304"
}


Suppression de donn�es (delete)

Ce chapitre d�taille les diff�rentes m�thodes pour supprimer des �l�ments de la base de donn�es :
Remarque : la diff�rence entre delete et destroy est li�e � la suppression logique (soft delete) d'un �l�ment.

delete_all / destroy_all

Les actions delete_all et destroy_all permettent de supprimer tous les �l�ments d'une table. La diff�rence entre delete et destroy est li�e � la suppression logique (soft delete) d'un �l�ment.

-- Exemple n�1 -- supprime tous les �l�ments de la table comment :

Requ�te JSON :
{
   "request_id": "7b06b5c0-409f-4e0d-bfc4-acafbfe7e796",
   "action": "delete_all",
   "entity": "comment"
}
R�ponse JSON :
{
   "data": {
      "deleted": true
   },
   "request_id": "7b06b5c0-409f-4e0d-bfc4-acafbfe7e796"
}


delete_by_query / destroy_by_query

Les actions delete_by_query et destroy_by_query permettent de supprimer les �l�ments d'une table en fonction d'une requ�te. La diff�rence entre delete et destroy est li�e � la suppression logique (soft delete) d'un �l�ment.

-- Exemple n�1 -- supprime les �l�ments de la table author qui ont un sexe de type female (female = enum de valeur 1) :

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


delete_by_id / destroy_by_id

Les actions delete_by_id et destroy_by_id permettent de supprimer les �l�ments d'une table en fonction de leur identifiant unique. La diff�rence entre delete et destroy est li�e � la suppression logique (soft delete) d'un �l�ment.

-- Exemple n�1 -- supprime de la base de donn�es le blog qui a pour identifiant unique la valeur 4 :

Requ�te JSON :
{
   "request_id": "80bff383-8ebd-4bde-bb42-37b6f67bc39f",
   "action": "delete_by_id",
   "entity": "blog",
   "data": {
      "blog_id": 4
   }
}
R�ponse JSON :
{
   "data": {
      "blog_id": 4
   },
   "request_id": "80bff383-8ebd-4bde-bb42-37b6f67bc39f"
}


-- Exemple n�2 -- supprime de la base de donn�es les blogs qui ont pour identifiant unique les valeurs 2 et 3 :

Requ�te JSON :
{
   "request_id": "38020cb7-d725-4c0e-80a0-63db7569155e",
   "action": "delete_by_id",
   "entity": "blog",
   "data": [
      {
         "blog_id": 3
      },
      {
         "blog_id": 2
      }
   ]
}
R�ponse JSON :
{
   "data": [
      {
         "blog_id": 3
      },
      {
         "blog_id": 2
      }
   ],
   "request_id": "38020cb7-d725-4c0e-80a0-63db7569155e"
}


Validation de donn�es (validate)

L'action validate permet de valider les propri�t�s d'une instance (sans d�clencher d'action sur la base de donn�es). L'action validate appelle le module QxValidator de la biblioth�que QxOrm.

-- Exemple n�1 -- un blog doit contenir du texte (propri�t� blog_text) pour pouvoir �tre sauvegard� dans la base de donn�es. La requ�te JSON suivante permet d'indiquer que l'instance est non valide avec un message explicite :

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


-- Exemple n�2 -- en ajoutant une valeur � la propri�t� blog_text, alors le blog devient valide (la r�ponse JSON dispose d'un champ invalid_values qui vaut null) :

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


Appel RAW SQL ou proc�dure stock�e

L'action call_custom_query permet d'appeler une requ�te SQL personnalis�e ou une proc�dure stock�e.

-- Exemple n�1 -- ins�re dans la base de donn�es un nouveau author avec une requ�te SQL personnalis�e :

Requ�te JSON :
{
   "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
         }
      ]
   }
}
R�ponse JSON :
{
   "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"
}


Appel fonctions natives C++

L'action call_entity_function permet d'appeler des fonctions natives C++ enregistr�es dans le contexte QxOrm.
Pr�requis : la fonction native C++ doit �tre une fonction static avec pour signature : static QJsonValue myNativeCppFct(const QJsonValue & request);

Voici un exemple d'enregistrement de fonction native C++ pouvant �tre appel�e par les API JSON de la biblioth�que QxOrm :

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;
}


Voici comment appeler cette fonction helloWorld avec les API JSON en utilisant l'action call_entity_function :

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


Meta-data (structure des classes C++ enregistr�es dans le contexte QxOrm)

L'action get_meta_data permet de r�cup�rer les m�ta-donn�es d'une ou de toutes les entit�s enregistr�es dans le contexte QxOrm (structure des classes avec liste des propri�t�s et relations).

-- Exemple n�1 -- r�cup�re toutes les m�ta-donn�es du projet d'exemple qxBlogRestApi :

Requ�te JSON :
{
   "request_id": "842ed7b5-9b94-455f-86dc-32992866b3d5",
   "action": "get_meta_data",
   "entity": "*"
}
R�ponse JSON :
{
   "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"
}


Envoyer une liste de requ�tes JSON

Afin de limiter le nombre de transactions entre le client et le serveur, il est possible d'envoyer une liste de requ�tes JSON au module QxRestApi. Chaque requ�te JSON de la liste peut disposer de son propre identifiant request_id (afin d'associer une r�ponse JSON � la requ�te correspondante). Lorsqu'une liste de requ�tes JSON est envoy�e au module QxRestApi, alors une transaction (commit/rollback) est automatiquement cr��e (ainsi en cas d'erreur, tous les traitements sur la base de donn�es sont annul�s).

-- Exemple n�1 -- envoi 4 requ�tes JSON au module QxRestApi (1 requ�te pour r�cup�rer les m�ta-donn�es du projet + 3 requ�tes fetch_all avec diff�rent niveau de r�cup�ration des relations) :

Requ�te JSON :
[
   {
      "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": [
         "*->*"
      ]
   }
]
R�ponse JSON :
[
   {
      "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-202XDL Teamty - ic-east.com