QxModel.h 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. /****************************************************************************
  2. **
  3. ** https://www.qxorm.com/
  4. ** Copyright (C) 2013 Lionel Marty (contact@qxorm.com)
  5. **
  6. ** This file is part of the QxOrm library
  7. **
  8. ** This software is provided 'as-is', without any express or implied
  9. ** warranty. In no event will the authors be held liable for any
  10. ** damages arising from the use of this software
  11. **
  12. ** Commercial Usage
  13. ** Licensees holding valid commercial QxOrm licenses may use this file in
  14. ** accordance with the commercial license agreement provided with the
  15. ** Software or, alternatively, in accordance with the terms contained in
  16. ** a written agreement between you and Lionel Marty
  17. **
  18. ** GNU General Public License Usage
  19. ** Alternatively, this file may be used under the terms of the GNU
  20. ** General Public License version 3.0 as published by the Free Software
  21. ** Foundation and appearing in the file 'license.gpl3.txt' included in the
  22. ** packaging of this file. Please review the following information to
  23. ** ensure the GNU General Public License version 3.0 requirements will be
  24. ** met : http://www.gnu.org/copyleft/gpl.html
  25. **
  26. ** If you are unsure which license is appropriate for your use, or
  27. ** if you have questions regarding the use of this file, please contact :
  28. ** contact@qxorm.com
  29. **
  30. ****************************************************************************/
  31. #ifndef _QX_MODEL_H_
  32. #define _QX_MODEL_H_
  33. #ifdef _MSC_VER
  34. #pragma once
  35. #endif
  36. /*!
  37. * \file QxModel.h
  38. * \author Lionel Marty
  39. * \ingroup QxModelView
  40. * \brief All classes registered into QxOrm context can be used with Qt model/view architecture (Qt widgets and/or QML views)
  41. */
  42. #include <QxModelView/IxModel.h>
  43. #include <QxModelView/QxModelRowCompare.h>
  44. #include <QxCollection/QxCollection.h>
  45. #include <QxRegister/QxClass.h>
  46. #include <QxTraits/get_primary_key.h>
  47. #include <QxTraits/is_qx_registered.h>
  48. #include <QxTraits/is_valid_primary_key.h>
  49. #include <QxSerialize/QxDump.h>
  50. #include <QxSerialize/QxClone.h>
  51. #ifndef _QX_NO_JSON
  52. #include <QxSerialize/QJson/QxSerializeQJson_QxCollection.h>
  53. #include <QxSerialize/QxSerializeQJson.h>
  54. #endif // _QX_NO_JSON
  55. namespace qx {
  56. namespace model_view {
  57. namespace detail {
  58. template <class T, class M> struct QxNestedModel;
  59. template <class T, class M> struct QxNestedModel_Generic;
  60. template <class T, class M> struct QxNestedModel_Container;
  61. } // namespace detail
  62. } // namespace model_view
  63. } // namespace qx
  64. namespace qx {
  65. /*!
  66. * \ingroup QxModelView
  67. * \brief qx::QxModel<T, B> : all classes registered into QxOrm context can be used with Qt model/view architecture (Qt widgets and/or QML views)
  68. *
  69. * <b>QxModelView</b> module provides an easy way to work with Qt model/view engine with all classes registered into QxOrm context :
  70. * - Qt widgets : QTableView or QListView for example to display/modify a database table content ;
  71. * - QML : each property defined in QxOrm context is exposed to QML engine : QxModelView module makes easier integration between QML and databases.
  72. *
  73. * qx::IxModel interface provides a generic way for all models linked to persistents classes registered into QxOrm context. All methods of this class prefixed by <i>qx</i> call functions from qx::dao namespace and then communicate with database.
  74. *
  75. * The <i>qxBlogModelView</i> sample project in <i>./test/</i> directory of QxOrm package shows how to create quickly a model and associate it to the Qt model/view engine (first with a Qt widget, then with a QML view).
  76. *
  77. * 1- Here is an example to display/modify data from 'author' table (go to qxBlog tutorial for 'author' class definition) in a QTableView :
  78. * \code
  79. // Create a model and fetch all data from database
  80. qx::IxModel * pModel = new qx::QxModel<author>();
  81. pModel->qxFetchAll();
  82. // Associate the model to a QTableView and display it
  83. QTableView tableView;
  84. tableView.setModel(pModel);
  85. tableView.show();
  86. * \endcode
  87. *
  88. * 2- Here is another example in QML (with Qt5, QxModelView module works fine with Qt4 too) :
  89. * \code
  90. // Create a model and fetch all data from database
  91. qx::IxModel * pModel = new qx::QxModel<author>();
  92. pModel->qxFetchAll();
  93. // Associate the model to a QML view and display it
  94. QQuickView qmlView;
  95. qmlView.rootContext()->setContextProperty("myModel", pModel);
  96. qmlView.setSource(QUrl("qrc:/documents/main.qml"));
  97. qmlView.show();
  98. * \endcode
  99. *
  100. * And here is the 'main.qml' file content :
  101. * \code
  102. import QtQuick 2.1
  103. import QtQuick.Controls 1.0
  104. Item {
  105. width: 400
  106. height: 300
  107. Row {
  108. height: 20
  109. spacing: 20
  110. Button {
  111. text: "Clear"
  112. onClicked: myModel.clear()
  113. }
  114. Button {
  115. text: "Fetch All"
  116. onClicked: myModel.qxFetchAll_()
  117. }
  118. Button {
  119. text: "Save"
  120. onClicked: myModel.qxSave_()
  121. }
  122. }
  123. ListView {
  124. y: 30
  125. height: 270
  126. model: myModel
  127. delegate: Row {
  128. height: 20
  129. spacing: 10
  130. Text { text: "id: " + author_id }
  131. TextField {
  132. text: name
  133. onTextChanged: name = text
  134. }
  135. }
  136. }
  137. }
  138. * \endcode
  139. *
  140. * As you can see in the 'main.qml' file, 'author_id' and 'name' properties of 'author' model ('myModel' variable) can be automatically read and write (because they are registered into QxOrm context).
  141. * Moreover, qx::IxModel interface provides a list of methods for QML side (Q_INVOKABLE) to communicate with database : for example, the 'Save' button will save the model in database without having to write a C++ function.
  142. *
  143. * <b>Note :</b> a <b>QxEntityEditor</b> plugin generates automatically the code to manage models with relationships. Then it is possible to work with nested C++ models.
  144. */
  145. template <class T, class B = qx::IxModel>
  146. class QxModel : public B // B type must inherit from qx::IxModel and must not define new data-members (use 'm_hCustomProperties' or QObject dynamic properties if you need new properties in your derived class)
  147. {
  148. template <typename U, typename V> friend struct qx::model_view::detail::QxNestedModel;
  149. template <typename U, typename V> friend struct qx::model_view::detail::QxNestedModel_Generic;
  150. template <typename U, typename V> friend struct qx::model_view::detail::QxNestedModel_Container;
  151. public:
  152. typedef std::shared_ptr<T> type_ptr;
  153. typedef typename qx::trait::get_primary_key<T>::type type_primary_key;
  154. typedef qx::QxCollection<type_primary_key, type_ptr> type_collection;
  155. typedef B type_base_class;
  156. enum { qx_is_valid = (qx::trait::is_qx_registered<T>::value && std::is_base_of<qx::IxModel, B>::value) };
  157. protected:
  158. type_collection m_model; //!< Model associated to a class registered into QxOrm context
  159. std::shared_ptr<QPair<int, type_ptr> > m_pDirtyRow; //!< When displayed in a QTableView, this will cause an empty line awaiting user input to be displayed at the bottom (enabled with setShowEmptyLine() method)
  160. public:
  161. QxModel(QObject * parent = 0) : B(parent) { qx::QxModel<T, B>::init(); }
  162. QxModel(qx::IxModel * other, QObject * parent) : B(parent) { qx::QxModel<T, B>::initFrom(other); }
  163. virtual ~QxModel() { ; }
  164. protected:
  165. void init()
  166. {
  167. static_assert(qx_is_valid, "qx_is_valid");
  168. this->m_pClass = qx::QxClass<T>::getSingleton(); qAssert(this->m_pClass != NULL);
  169. this->m_pDataMemberX = (this->m_pClass ? this->m_pClass->getDataMemberX() : NULL); qAssert(this->m_pDataMemberX != NULL);
  170. this->m_pDataMemberId = (this->m_pDataMemberX ? this->m_pDataMemberX->getId_WithDaoStrategy() : NULL);
  171. this->m_pCollection = (& m_model);
  172. this->generateRoleNames();
  173. }
  174. void initFrom(qx::IxModel * pOther)
  175. {
  176. init();
  177. qx::QxModel<T, B> * pOtherWrk = static_cast<qx::QxModel<T, B> *>(pOther);
  178. m_model = pOtherWrk->m_model;
  179. this->m_lManualInsertIndex = pOtherWrk->m_lManualInsertIndex;
  180. this->setParentModel(pOtherWrk->m_pParent);
  181. if (this->m_pParent) { this->m_eAutoUpdateDatabase = this->m_pParent->getAutoUpdateDatabase(); }
  182. this->m_hCustomProperties = pOtherWrk->m_hCustomProperties;
  183. }
  184. public:
  185. virtual bool insertRows(int row, int count, const QModelIndex & parent = QModelIndex())
  186. {
  187. if (parent.isValid()) { return false; }
  188. if ((row < 0) || (count <= 0)) { return false; }
  189. this->beginInsertRows(QModelIndex(), row, (row + count - 1));
  190. for (int i = 0; i < count; ++i)
  191. {
  192. type_ptr pItem = type_ptr(new T());
  193. insertItem(row, pItem);
  194. }
  195. this->endInsertRows();
  196. return true;
  197. }
  198. virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder)
  199. {
  200. IxDataMember * pDataMember = this->getDataMember(column); if (! pDataMember) { return; }
  201. m_model.sort(qx::model_view::QxModelRowCompare<typename type_collection::type_pair_key_value>((order == Qt::AscendingOrder), pDataMember));
  202. this->raiseEvent_layoutChanged();
  203. }
  204. virtual bool getShowEmptyLine() const { return m_pDirtyRow.get(); }
  205. virtual void setShowEmptyLine(bool b)
  206. {
  207. if (b == getShowEmptyLine()) { return; }
  208. if (b) { addDirtyRow(); return; }
  209. this->beginRemoveRows(QModelIndex(), this->rowCount(), this->rowCount());
  210. m_pDirtyRow.reset();
  211. this->endRemoveRows();
  212. }
  213. public:
  214. /*!
  215. * \brief Return the number of lines in the table (database) mapped to the C++ class T (registered into QxOrm context) and filtered by a user SQL query
  216. * \param query Define a user SQL query added to default SQL query builded by QxOrm library (optional parameter)
  217. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  218. */
  219. virtual long qxCount(const qx::QxSqlQuery & query = qx::QxSqlQuery(), QSqlDatabase * pDatabase = NULL)
  220. {
  221. return qx::dao::count<T>(query, this->database(pDatabase));
  222. }
  223. /*!
  224. * \brief Return the number of lines in the table (database) mapped to the C++ class T (registered into QxOrm context) and filtered by a user SQL query
  225. * \param lCount Output parameter with the number of lines in the table associated to the SQL query
  226. * \param query Define a user SQL query added to default SQL query builded by QxOrm library (optional parameter)
  227. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  228. */
  229. virtual QSqlError qxCount(long & lCount, const qx::QxSqlQuery & query = qx::QxSqlQuery(), QSqlDatabase * pDatabase = NULL)
  230. {
  231. this->m_lastError = qx::dao::count<T>(lCount, query, this->database(pDatabase));
  232. return this->m_lastError;
  233. }
  234. /*!
  235. * \brief Clear the model and fetch an object (retrieve all its properties) of type T (registered into QxOrm context) mapped to a table in the database
  236. * \param id Row id to be fetched (retrieve all properties from database)
  237. * \param relation List of relationships keys to be fetched (eager fetch instead of default lazy fetch for a relation) : use "|" separator to put many relationships keys into this parameter
  238. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  239. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  240. */
  241. virtual QSqlError qxFetchById(const QVariant & id, const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL)
  242. {
  243. this->clear();
  244. type_ptr pItem = type_ptr(new T());
  245. if (! this->m_pDataMemberId) { qDebug("[QxOrm] problem with 'qxFetchById()' method : '%s'", "data member id not registered"); qAssert(false); }
  246. if (! this->m_pDataMemberId) { this->m_lastError = QSqlError("[QxOrm] problem with 'qxFetchById()' method : 'data member id not registered'", "", QSqlError::UnknownError); return this->m_lastError; }
  247. this->m_pDataMemberId->fromVariant(pItem.get(), id);
  248. type_primary_key primaryKey;
  249. qx::cvt::from_variant(id, primaryKey);
  250. this->beginInsertRows(QModelIndex(), 0, 0);
  251. m_model.insert(primaryKey, pItem);
  252. if (relation.count() == 0) { this->m_lastError = qx::dao::fetch_by_id((* pItem), this->database(pDatabase), this->m_lstColumns); }
  253. else { this->m_lastError = qx::dao::fetch_by_id_with_relation(relation, (* pItem), this->database(pDatabase)); }
  254. this->updateShowEmptyLine();
  255. this->endInsertRows();
  256. return this->m_lastError;
  257. }
  258. /*!
  259. * \brief Clear the model and fetch a list of objects (retrieve all elements and properties associated) of type T (container registered into QxOrm context) mapped to a table in the database
  260. * \param relation List of relationships keys to be fetched (eager fetch instead of default lazy fetch for a relation) : use "|" separator to put many relationships keys into this parameter
  261. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  262. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  263. */
  264. virtual QSqlError qxFetchAll(const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL)
  265. {
  266. this->clear();
  267. type_collection tmp;
  268. if (relation.count() == 0) { this->m_lastError = qx::dao::fetch_all(tmp, this->database(pDatabase), this->m_lstColumns); }
  269. else { this->m_lastError = qx::dao::fetch_all_with_relation(relation, tmp, this->database(pDatabase)); }
  270. if (tmp.count() <= 0) { return this->m_lastError; }
  271. this->beginResetModel();
  272. m_model = tmp;
  273. this->updateShowEmptyLine();
  274. this->endResetModel();
  275. return this->m_lastError;
  276. }
  277. /*!
  278. * \brief Clear the model and fetch a list of objects (retrieve all elements and properties associated) of type T (container registered into QxOrm context) mapped to a table in the database and filtered by a user SQL query
  279. * \param query Define a user SQL query added to default SQL query builded by QxOrm library
  280. * \param relation List of relationships keys to be fetched (eager fetch instead of default lazy fetch for a relation) : use "|" separator to put many relationships keys into this parameter
  281. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  282. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  283. */
  284. virtual QSqlError qxFetchByQuery(const qx::QxSqlQuery & query, const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL)
  285. {
  286. this->clear();
  287. type_collection tmp;
  288. if (relation.count() == 0) { this->m_lastError = qx::dao::fetch_by_query(query, tmp, this->database(pDatabase), this->m_lstColumns); }
  289. else { this->m_lastError = qx::dao::fetch_by_query_with_relation(relation, query, tmp, this->database(pDatabase)); }
  290. if (tmp.count() <= 0) { return this->m_lastError; }
  291. this->beginResetModel();
  292. m_model = tmp;
  293. this->updateShowEmptyLine();
  294. this->endResetModel();
  295. return this->m_lastError;
  296. }
  297. /*!
  298. * \brief Get an item in the model at line row and fetch all its properties mapped to a table in the database, then all views attached to this model are automatically updated
  299. * \param row Get an item in the model at line row
  300. * \param relation List of relationships keys to be fetched (eager fetch instead of default lazy fetch for a relation) : use "|" separator to put many relationships keys into this parameter
  301. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  302. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  303. */
  304. virtual QSqlError qxFetchRow(int row, const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL)
  305. {
  306. type_ptr pItem = getRowItemAt(row); if (! pItem) { return QSqlError(); }
  307. if (relation.count() == 0) { this->m_lastError = qx::dao::fetch_by_id((* pItem), this->database(pDatabase), this->m_lstColumns); }
  308. else { this->m_lastError = qx::dao::fetch_by_id_with_relation(relation, (* pItem), this->database(pDatabase)); }
  309. if (this->m_lastError.isValid()) { return this->m_lastError; }
  310. QModelIndex idxTopLeft = this->index(row, 0);
  311. QModelIndex idxBottomRight = this->index(row, (this->m_lstDataMember.count() - 1));
  312. this->raiseEvent_dataChanged(idxTopLeft, idxBottomRight);
  313. updateKey(row);
  314. return this->m_lastError;
  315. }
  316. /*!
  317. * \brief Insert all items in the model into database
  318. * \param relation List of relationships keys to be inserted in others tables of database : use "|" separator to put many relationships keys into this parameter
  319. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  320. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  321. */
  322. virtual QSqlError qxInsert(const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL)
  323. {
  324. if (relation.count() > 0) { this->syncAllNestedModel(relation); }
  325. if (relation.count() == 0) { this->m_lastError = qx::dao::insert(m_model, this->database(pDatabase)); }
  326. else { this->m_lastError = qx::dao::insert_with_relation(relation, m_model, this->database(pDatabase)); }
  327. if (! this->m_lastError.isValid()) { this->updateAllKeys(); }
  328. return this->m_lastError;
  329. }
  330. /*!
  331. * \brief Insert an item of the model at line row into database
  332. * \param row Insert an item in the model at line row
  333. * \param relation List of relationships keys to be inserted in others tables of database : use "|" separator to put many relationships keys into this parameter
  334. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  335. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  336. */
  337. virtual QSqlError qxInsertRow(int row, const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL)
  338. {
  339. type_ptr pItem = getRowItemAt(row); if (! pItem) { return QSqlError(); }
  340. if (relation.count() > 0) { this->syncNestedModel(row, relation); }
  341. if (relation.count() == 0) { this->m_lastError = qx::dao::insert((* pItem), this->database(pDatabase)); }
  342. else { this->m_lastError = qx::dao::insert_with_relation(relation, (* pItem), this->database(pDatabase)); }
  343. if (! this->m_lastError.isValid()) { updateKey(row); }
  344. return this->m_lastError;
  345. }
  346. /*!
  347. * \brief Update all items in the model into database
  348. * \param query Define a user SQL query added to default SQL query builded by QxOrm library
  349. * \param relation List of relationships keys to be inserted in others tables of database : use "|" separator to put many relationships keys into this parameter
  350. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  351. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  352. */
  353. virtual QSqlError qxUpdate(const qx::QxSqlQuery & query = qx::QxSqlQuery(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL)
  354. {
  355. if (relation.count() > 0) { this->syncAllNestedModel(relation); }
  356. if (relation.count() == 0) { this->m_lastError = qx::dao::update_by_query(query, m_model, this->database(pDatabase), this->m_lstColumns); }
  357. else { this->m_lastError = qx::dao::update_by_query_with_relation(relation, query, m_model, this->database(pDatabase)); }
  358. if (! this->m_lastError.isValid()) { this->updateAllKeys(); }
  359. return this->m_lastError;
  360. }
  361. /*!
  362. * \brief Update an item of the model at line row into database
  363. * \param row Update an item in the model at line row
  364. * \param query Define a user SQL query added to default SQL query builded by QxOrm library
  365. * \param relation List of relationships keys to be inserted in others tables of database : use "|" separator to put many relationships keys into this parameter
  366. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  367. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  368. */
  369. virtual QSqlError qxUpdateRow(int row, const qx::QxSqlQuery & query = qx::QxSqlQuery(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL)
  370. {
  371. if ((row < 0) || (row >= m_model.count())) { return QSqlError(); }
  372. if (relation.count() > 0) { this->syncNestedModel(row, relation); }
  373. type_ptr pItem = m_model.getByIndex(row); if (! pItem) { return QSqlError(); }
  374. if (relation.count() == 0) { this->m_lastError = qx::dao::update_by_query(query, (* pItem), this->database(pDatabase), this->m_lstColumns); }
  375. else { this->m_lastError = qx::dao::update_by_query_with_relation(relation, query, (* pItem), this->database(pDatabase)); }
  376. if (! this->m_lastError.isValid()) { updateKey(row); }
  377. return this->m_lastError;
  378. }
  379. /*!
  380. * \brief Save all items (insert or update) in the model into database
  381. * \param relation List of relationships keys to be inserted in others tables of database : use "|" separator to put many relationships keys into this parameter
  382. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  383. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  384. */
  385. virtual QSqlError qxSave(const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL)
  386. {
  387. if (relation.count() > 0) { this->syncAllNestedModel(relation); }
  388. if (relation.count() == 0) { this->m_lastError = qx::dao::save(m_model, this->database(pDatabase)); }
  389. else { this->m_lastError = qx::dao::save_with_relation(relation, m_model, this->database(pDatabase)); }
  390. if (! this->m_lastError.isValid()) { this->updateAllKeys(); }
  391. return this->m_lastError;
  392. }
  393. /*!
  394. * \brief Save an item of the model at line row into database
  395. * \param row Save an item (insert or update) in the model at line row
  396. * \param relation List of relationships keys to be inserted in others tables of database : use "|" separator to put many relationships keys into this parameter
  397. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  398. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  399. */
  400. virtual QSqlError qxSaveRow(int row, const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL)
  401. {
  402. if ((row < 0) || (row >= m_model.count())) { return QSqlError(); }
  403. if (relation.count() > 0) { this->syncNestedModel(row, relation); }
  404. type_ptr pItem = m_model.getByIndex(row); if (! pItem) { return QSqlError(); }
  405. if (relation.count() == 0) { this->m_lastError = qx::dao::save((* pItem), this->database(pDatabase)); }
  406. else { this->m_lastError = qx::dao::save_with_relation(relation, (* pItem), this->database(pDatabase)); }
  407. if (! this->m_lastError.isValid()) { updateKey(row); }
  408. return this->m_lastError;
  409. }
  410. /*!
  411. * \brief Used internally by qx::IxModel::setData() method with e_auto_update_on_field_change option, save an item (even if it is the dirty row item) of the model at line row into database
  412. * \param row Save an item (insert or update) in the model at line row
  413. * \param column List of columns of model to save in database
  414. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  415. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  416. */
  417. virtual QSqlError qxSaveRowData(int row, const QStringList & column = QStringList(), QSqlDatabase * pDatabase = NULL)
  418. {
  419. if (! this->m_pDataMemberId) { this->m_lastError = QSqlError("[QxOrm] problem with 'qxSaveRowData()' method : 'data member id not registered'", "", QSqlError::UnknownError); return this->m_lastError; }
  420. type_ptr pItem = getRowItemAt(row); if (! pItem) { return QSqlError(); }
  421. QVariant id = this->m_pDataMemberId->toVariant(pItem.get());
  422. bool bExist = qx::trait::is_valid_primary_key(id);
  423. if (bExist) { bExist = qx::dao::exist((* pItem), this->database(pDatabase)); }
  424. if (bExist) { this->m_lastError = qx::dao::update((* pItem), this->database(pDatabase), column); }
  425. else { this->m_lastError = qx::dao::insert((* pItem), this->database(pDatabase)); }
  426. return this->m_lastError;
  427. }
  428. /*!
  429. * \brief Delete a line of a table (database) mapped to a C++ object of type T (registered into QxOrm context), if no error occurred then you should remove row from the model
  430. * \param id Row id to be deleted from database
  431. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  432. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  433. */
  434. virtual QSqlError qxDeleteById(const QVariant & id, QSqlDatabase * pDatabase = NULL)
  435. {
  436. type_ptr pItem = type_ptr(new T());
  437. if (! this->m_pDataMemberId) { qDebug("[QxOrm] problem with 'qxDeleteById()' method : '%s'", "data member id not registered"); qAssert(false); }
  438. if (! this->m_pDataMemberId) { this->m_lastError = QSqlError("[QxOrm] problem with 'qxDeleteById()' method : 'data member id not registered'", "", QSqlError::UnknownError); return this->m_lastError; }
  439. this->m_pDataMemberId->fromVariant(pItem.get(), id);
  440. this->m_lastError = qx::dao::delete_by_id((* pItem), this->database(pDatabase));
  441. return this->m_lastError;
  442. }
  443. /*!
  444. * \brief Delete all lines of a table (database) mapped to a C++ class T (registered into QxOrm context), if no error occurred then you should clear the model
  445. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  446. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  447. */
  448. virtual QSqlError qxDeleteAll(QSqlDatabase * pDatabase = NULL)
  449. {
  450. this->m_lastError = qx::dao::delete_all<T>(this->database(pDatabase));
  451. return this->m_lastError;
  452. }
  453. /*!
  454. * \brief Delete all lines of a table (database) mapped to a C++ class T (registered into QxOrm context) and filtered by a user SQL query, if no error occurred then you should refresh the model
  455. * \param query Define a user SQL query added to default SQL query builded by QxOrm library
  456. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  457. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  458. */
  459. virtual QSqlError qxDeleteByQuery(const qx::QxSqlQuery & query, QSqlDatabase * pDatabase = NULL)
  460. {
  461. this->m_lastError = qx::dao::delete_by_query<T>(query, this->database(pDatabase));
  462. return this->m_lastError;
  463. }
  464. /*!
  465. * \brief Delete in database the item at line row in the model, if no error occurred then you should remove row from the model
  466. * \param row Delete in database the item in the model at line row
  467. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  468. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  469. */
  470. virtual QSqlError qxDeleteRow(int row, QSqlDatabase * pDatabase = NULL)
  471. {
  472. if ((row < 0) || (row >= m_model.count())) { return QSqlError(); }
  473. type_ptr pItem = m_model.getByIndex(row); if (! pItem) { return QSqlError(); }
  474. this->m_lastError = qx::dao::delete_by_id((* pItem), this->database(pDatabase));
  475. return this->m_lastError;
  476. }
  477. /*!
  478. * \brief Delete a line of a table (even if a logical delete is defined) mapped to a C++ object of type T (registered into QxOrm context), if no error occurred then you should remove row from the model
  479. * \param id Row id to be deleted from database
  480. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  481. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  482. */
  483. virtual QSqlError qxDestroyById(const QVariant & id, QSqlDatabase * pDatabase = NULL)
  484. {
  485. type_ptr pItem = type_ptr(new T());
  486. if (! this->m_pDataMemberId) { qDebug("[QxOrm] problem with 'qxDeleteById()' method : '%s'", "data member id not registered"); qAssert(false); }
  487. if (! this->m_pDataMemberId) { this->m_lastError = QSqlError("[QxOrm] problem with 'qxDeleteById()' method : 'data member id not registered'", "", QSqlError::UnknownError); return this->m_lastError; }
  488. this->m_pDataMemberId->fromVariant(pItem.get(), id);
  489. this->m_lastError = qx::dao::destroy_by_id((* pItem), this->database(pDatabase));
  490. return this->m_lastError;
  491. }
  492. /*!
  493. * \brief Delete all lines of a table (even if a logical delete is defined) mapped to a C++ class T (registered into QxOrm context), if no error occurred then you should clear the model
  494. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  495. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  496. */
  497. virtual QSqlError qxDestroyAll(QSqlDatabase * pDatabase = NULL)
  498. {
  499. this->m_lastError = qx::dao::destroy_all<T>(this->database(pDatabase));
  500. return this->m_lastError;
  501. }
  502. /*!
  503. * \brief Delete all lines of a table (even if a logical delete is defined) mapped to a C++ class T (registered into QxOrm context) and filtered by a user SQL query, if no error occurred then you should refresh the model
  504. * \param query Define a user SQL query added to default SQL query builded by QxOrm library
  505. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  506. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  507. */
  508. virtual QSqlError qxDestroyByQuery(const qx::QxSqlQuery & query, QSqlDatabase * pDatabase = NULL)
  509. {
  510. this->m_lastError = qx::dao::destroy_by_query<T>(query, this->database(pDatabase));
  511. return this->m_lastError;
  512. }
  513. /*!
  514. * \brief Delete in database (even if a logical delete is defined) the item at line row in the model, if no error occurred then you should remove row from the model
  515. * \param row Delete in database the item in the model at line row
  516. * \param pDatabase Connection to database (you can manage your own connection pool for example, you can also define a transaction, etc.); if NULL, a valid connection for the current thread is provided by qx::QxSqlDatabase singleton class (optional parameter)
  517. * \return Empty QSqlError object (from Qt library) if no error occurred; otherwise QSqlError contains a description of database error executing SQL query
  518. */
  519. virtual QSqlError qxDestroyRow(int row, QSqlDatabase * pDatabase = NULL)
  520. {
  521. if ((row < 0) || (row >= m_model.count())) { return QSqlError(); }
  522. type_ptr pItem = m_model.getByIndex(row); if (! pItem) { return QSqlError(); }
  523. this->m_lastError = qx::dao::destroy_by_id((* pItem), this->database(pDatabase));
  524. return this->m_lastError;
  525. }
  526. virtual QSqlError qxExecuteQuery(qx::QxSqlQuery & query, QSqlDatabase * pDatabase = NULL)
  527. {
  528. this->clear();
  529. type_collection tmp;
  530. this->m_lastError = qx::dao::execute_query(query, tmp, this->database(pDatabase));
  531. if (tmp.count() <= 0) { return this->m_lastError; }
  532. this->beginResetModel();
  533. m_model = tmp;
  534. this->updateShowEmptyLine();
  535. this->endResetModel();
  536. return this->m_lastError;
  537. }
  538. virtual qx_bool qxExist(const QVariant & id, QSqlDatabase * pDatabase = NULL)
  539. {
  540. type_ptr pItem = type_ptr(new T());
  541. if (! this->m_pDataMemberId) { qDebug("[QxOrm] problem with 'qxExist()' method : '%s'", "data member id not registered"); qAssert(false); }
  542. if (! this->m_pDataMemberId) { return qx_bool(false); }
  543. this->m_pDataMemberId->fromVariant(pItem.get(), id);
  544. return qx::dao::exist((* pItem), this->database(pDatabase));
  545. }
  546. virtual qx::QxInvalidValueX qxValidate(const QStringList & groups = QStringList())
  547. {
  548. return qx::validate(m_model, groups);
  549. }
  550. virtual qx::QxInvalidValueX qxValidateRow(int row, const QStringList & groups = QStringList())
  551. {
  552. type_ptr pItem = getRowItemAt(row); if (! pItem) { return qx::QxInvalidValueX(); }
  553. return qx::validate((* pItem), groups);
  554. }
  555. protected:
  556. type_ptr getRowItemAt(int row) const
  557. {
  558. if ((row >= 0) && (row < m_model.count())) { return m_model.getByIndex(row); }
  559. if (m_pDirtyRow && (m_pDirtyRow->first == row)) { return m_pDirtyRow->second; }
  560. return type_ptr();
  561. }
  562. virtual void * getRowItemAsVoidPtr(int row) const { return getRowItemAt(row).get(); }
  563. virtual void dumpModelImpl(bool bJsonFormat) const { qx::dump(m_model, bJsonFormat); }
  564. virtual QObject * cloneModelImpl()
  565. {
  566. qx::QxModel<T, B> * pClone = new qx::QxModel<T, B>(this, NULL);
  567. std::shared_ptr<type_collection> pModel = qx::clone(pClone->m_model);
  568. if (pModel) { pClone->m_model = (* pModel); }
  569. return static_cast<QObject *>(pClone);
  570. }
  571. virtual void updateShowEmptyLine() { if (m_pDirtyRow) { m_pDirtyRow->first = m_model.count(); } }
  572. virtual bool isDirtyRow(int row) const { return (m_pDirtyRow && (m_pDirtyRow->first == row)); }
  573. virtual void insertDirtyRowToModel()
  574. {
  575. if (! m_pDirtyRow) { return; }
  576. int row = m_pDirtyRow->first;
  577. insertItem(row, m_pDirtyRow->second);
  578. if (this->m_pParent) { this->m_pParent->saveChildRelations(this); }
  579. updateKey(row);
  580. addDirtyRow();
  581. }
  582. void addDirtyRow()
  583. {
  584. this->beginInsertRows(QModelIndex(), m_model.count(), m_model.count());
  585. m_pDirtyRow.reset(new QPair<int, type_ptr>(m_model.count(), type_ptr(new T())));
  586. IxSqlRelation * pRelationParent = (this->m_pDataMemberRelationToParent ? this->m_pDataMemberRelationToParent->getSqlRelation() : NULL);
  587. IxSqlRelation::relation_type eRelationParentType = (pRelationParent ? pRelationParent->getRelationType() : IxSqlRelation::no_relation);
  588. if (this->m_pParent && ((eRelationParentType == IxSqlRelation::many_to_many) || (eRelationParentType == IxSqlRelation::many_to_one)))
  589. { this->m_pDataMemberRelationToParent->fromVariant(m_pDirtyRow->second.get(), this->m_pParent->getIdFromChild(this)); }
  590. this->endInsertRows();
  591. }
  592. void insertItem(int row, const type_ptr & pItem)
  593. {
  594. if (! pItem) { return; }
  595. type_primary_key primaryKey;
  596. this->m_lManualInsertIndex = (this->m_lManualInsertIndex - 1);
  597. QVariant vNewId(static_cast<qlonglong>(this->m_lManualInsertIndex));
  598. qx::cvt::from_variant(vNewId, primaryKey);
  599. m_model.insert(row, primaryKey, pItem);
  600. this->updateShowEmptyLine();
  601. }
  602. void updateKey(int row)
  603. {
  604. if ((row < 0) || (row >= m_model.count())) { return; }
  605. type_ptr pItem = m_model.getByIndex(row); if (! pItem || ! this->m_pDataMemberId) { return; }
  606. type_primary_key currPrimaryKey = m_model.getKeyByIndex(row);
  607. QVariant vCurrPrimaryKey = qx::cvt::to_variant(currPrimaryKey);
  608. QVariant vNextPrimaryKey = this->m_pDataMemberId->toVariant(pItem.get());
  609. if ((vCurrPrimaryKey == vNextPrimaryKey) || (! vNextPrimaryKey.isValid())) { return; }
  610. if (! qx::trait::is_valid_primary_key(vNextPrimaryKey)) { return; }
  611. type_primary_key updatedPrimaryKey;
  612. qx::cvt::from_variant(vNextPrimaryKey, updatedPrimaryKey);
  613. if (m_model.exist(updatedPrimaryKey)) { return; }
  614. m_model.removeByIndex(row);
  615. m_model.insert(row, updatedPrimaryKey, pItem);
  616. }
  617. void updateAllKeys()
  618. {
  619. for (long l = 0; l < m_model.count(); l++)
  620. { updateKey(l); }
  621. }
  622. protected:
  623. #ifndef _QX_NO_JSON
  624. virtual QString toJson_Helper(int row) const
  625. {
  626. if (row == -1) { return qx::serialization::json::to_string(m_model); }
  627. type_ptr pItem = getRowItemAt(row); if (! pItem) { return QString(); }
  628. return qx::serialization::json::to_string(* pItem);
  629. }
  630. virtual bool fromJson_Helper(const QString & json, int row)
  631. {
  632. if (row == -1)
  633. {
  634. this->clear();
  635. type_collection tmp;
  636. if (! qx::serialization::json::from_string(tmp, json)) { return false; }
  637. this->beginResetModel();
  638. m_model = tmp;
  639. this->updateShowEmptyLine();
  640. this->endResetModel();
  641. return true;
  642. }
  643. type_ptr pItem = getRowItemAt(row); if (! pItem) { return false; }
  644. if (! qx::serialization::json::from_string((* pItem), json)) { return false; }
  645. QModelIndex idxTopLeft = this->index(row, 0);
  646. QModelIndex idxBottomRight = this->index(row, (this->m_lstDataMember.count() - 1));
  647. this->raiseEvent_dataChanged(idxTopLeft, idxBottomRight);
  648. updateKey(row);
  649. return true;
  650. }
  651. virtual QVariant getRelationshipValues_Helper(int row, const QString & relation, bool bLoadFromDatabase, const QString & sAppendRelations)
  652. {
  653. if ((row < 0) || (row >= m_model.count())) { return QVariant(); }
  654. if (! this->m_pDataMemberId || ! this->m_pDataMemberX || ! this->m_pDataMemberX->exist(relation)) { return QVariant(); }
  655. IxDataMember * pDataMember = this->m_pDataMemberX->get_WithDaoStrategy(relation); if (! pDataMember) { return QVariant(); }
  656. IxSqlRelation * pRelation = (pDataMember->hasSqlRelation() ? pDataMember->getSqlRelation() : NULL); if (! pRelation) { return QVariant(); }
  657. type_ptr pItem = m_model.getByIndex(row); if (! pItem) { return QVariant(); }
  658. type_ptr pItemTemp = pItem;
  659. if (bLoadFromDatabase)
  660. {
  661. QString sRelation = relation;
  662. if (! sAppendRelations.isEmpty() && ! sAppendRelations.startsWith("->") && ! sAppendRelations.startsWith(">>")) { sRelation += "->" + sAppendRelations; }
  663. else if (! sAppendRelations.isEmpty()) { sRelation += sAppendRelations; }
  664. pItemTemp = type_ptr(new T());
  665. QVariant id = this->m_pDataMemberId->toVariant(pItem.get());
  666. this->m_pDataMemberId->fromVariant(pItemTemp.get(), id);
  667. QSqlError daoError = qx::dao::fetch_by_id_with_relation(sRelation, (* pItemTemp));
  668. if (daoError.isValid()) { return QVariant(); }
  669. }
  670. QJsonValue json = pDataMember->toJson(pItemTemp.get()); if (json.isNull()) { return QVariant(); }
  671. if (json.isArray()) { return json.toArray().toVariantList(); }
  672. return json.toObject().toVariantMap();
  673. }
  674. virtual bool setRelationshipValues_Helper(int row, const QString & relation, const QVariant & values)
  675. {
  676. if ((row < 0) || (row >= m_model.count())) { return false; }
  677. if ((values.type() != QVariant::List) && (values.type() != QVariant::Map)) { return false; }
  678. if (! this->m_pDataMemberId || ! this->m_pDataMemberX || ! this->m_pDataMemberX->exist(relation)) { return false; }
  679. IxDataMember * pDataMember = this->m_pDataMemberX->get_WithDaoStrategy(relation); if (! pDataMember) { return false; }
  680. IxSqlRelation * pRelation = (pDataMember->hasSqlRelation() ? pDataMember->getSqlRelation() : NULL); if (! pRelation) { return false; }
  681. type_ptr pItem = m_model.getByIndex(row); if (! pItem) { return false; }
  682. QJsonValue json;
  683. if (values.type() == QVariant::List) { json = QJsonArray::fromVariantList(values.toList()); }
  684. else if (values.type() == QVariant::Map) { json = QJsonObject::fromVariantMap(values.toMap()); }
  685. if (! pDataMember->fromJson(pItem.get(), json)) { return false; }
  686. QModelIndex idxTopLeft = this->index(row, 0);
  687. QModelIndex idxBottomRight = this->index(row, (this->m_lstDataMember.count() - 1));
  688. this->raiseEvent_dataChanged(idxTopLeft, idxBottomRight);
  689. return true;
  690. }
  691. #endif // _QX_NO_JSON
  692. };
  693. } // namespace qx
  694. #endif // _QX_MODEL_H_