EasyQtSql_Transaction.h 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. #ifndef EASYQTSQL_TRANSACTION_H
  2. #define EASYQTSQL_TRANSACTION_H
  3. /*
  4. * The MIT License (MIT)
  5. * Copyright 2018 Alexey Kramin
  6. *
  7. * Permission is hereby granted, free of charge, to any person obtaining
  8. * a copy of this software and associated documentation files (the
  9. * "Software"), to deal in the Software without restriction, including
  10. * without limitation the rights to use, copy, modify, merge, publish,
  11. * distribute, sublicense, and/or sell copies of the Software, and to
  12. * permit persons to whom the Software is furnished to do so, subject to
  13. * the following conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be
  16. * included in all copies or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  19. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  20. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  21. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  22. * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  23. * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  24. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  25. *
  26. */
  27. #ifndef EASY_QT_SQL_MAIN
  28. #include <QtSql>
  29. #include "EasyQtSql_DBException.h"
  30. #include "EasyQtSql_NonQueryResult.h"
  31. #include "EasyQtSql_InsertQuery.h"
  32. #include "EasyQtSql_DeleteQuery.h"
  33. #include "EasyQtSql_UpdateQuery.h"
  34. #include "EasyQtSql_PreparedQuery.h"
  35. #endif
  36. #include "EasyQtSql_Util.h"
  37. /*!
  38. \brief QSqlDatabase wrapper.
  39. \code
  40. void test()
  41. {
  42. QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
  43. db.setDatabaseName(":memory:");
  44. try
  45. {
  46. Database sdb(db);
  47. sdb.execNonQuery("CREATE TABLE table (a int, b int, c int, d text)");
  48. }
  49. catch (const DBException &e)
  50. {
  51. //you can handle all the errors at one point
  52. qDebug() << e.lastError << e.lastQuery;
  53. }
  54. }
  55. \endcode
  56. */
  57. class Database
  58. {
  59. Q_DISABLE_COPY(Database)
  60. public:
  61. /*!
  62. * \param db QSqlDatabase to use
  63. *
  64. * Creates an Database object, tries to open <em>db</em> connection if not opened.
  65. *
  66. * \throws DBException
  67. */
  68. explicit Database (const QSqlDatabase &db = QSqlDatabase())
  69. {
  70. m_db = db.isValid() ? db : QSqlDatabase::database();
  71. if (!m_db.isOpen())
  72. {
  73. if (!m_db.open())
  74. {
  75. #ifdef DB_EXCEPTIONS_ENABLED
  76. throw DBException(m_db);
  77. #endif
  78. }
  79. }
  80. }
  81. Database(Database&& other)
  82. {
  83. m_db = other.m_db;
  84. other.m_db = QSqlDatabase();
  85. }
  86. Database& operator=(Database&& other)
  87. {
  88. if (this == &other) return *this;
  89. m_db = other.m_db;
  90. other.m_db = QSqlDatabase();
  91. return *this;
  92. }
  93. /*!
  94. * \brief Returns information about the last error that occurred on the underlying database.
  95. */
  96. QSqlError lastError() const
  97. {
  98. return m_db.lastError();
  99. }
  100. /*!
  101. \brief Executes non-query SQL statement (DELETE, INSERT, UPDATE, CREATE, ALTER, etc.)
  102. \param query SQL statement string
  103. \throws DBException
  104. */
  105. NonQueryResult execNonQuery(const QString &sql) const
  106. {
  107. QSqlQuery q = m_db.exec(sql);
  108. #ifdef DB_EXCEPTIONS_ENABLED
  109. QSqlError lastError = q.lastError();
  110. if (lastError.isValid())
  111. throw DBException(q);
  112. #endif
  113. return NonQueryResult(q);
  114. }
  115. /*!
  116. \brief Executes SELECT query
  117. \param query SQL statement string
  118. \throws DBException
  119. */
  120. QueryResult execQuery(const QString &sql) const
  121. {
  122. QSqlQuery q = m_db.exec(sql);
  123. #ifdef DB_EXCEPTIONS_ENABLED
  124. QSqlError lastError = q.lastError();
  125. if (lastError.isValid())
  126. throw DBException(q);
  127. #endif
  128. return QueryResult(q);
  129. }
  130. /*!
  131. \brief Creates INSERT query wrapper
  132. \param table Table to insert into with list of columns
  133. */
  134. InsertQuery insertInto(const QString &table) const
  135. {
  136. InsertQuery query(table, m_db);
  137. return query;
  138. }
  139. /*!
  140. \brief Creates DELETE query wrapper
  141. \param table Table to delete from
  142. */
  143. DeleteQuery deleteFrom(const QString &table) const
  144. {
  145. DeleteQuery query(table, m_db);
  146. return query;
  147. }
  148. /*!
  149. \brief Creates UPDATE query wrapper
  150. \param table Table to update
  151. */
  152. UpdateQuery update(const QString &table) const
  153. {
  154. UpdateQuery query(table, m_db);
  155. return query;
  156. }
  157. /*!
  158. \brief Prepares SQL statement
  159. \param sql SQL statement string
  160. \param forwardOnly Configure underlying QSqlQuery as forwardOnly
  161. */
  162. PreparedQuery prepare(const QString &sql, bool forwardOnly = true) const
  163. {
  164. PreparedQuery query(sql, m_db, forwardOnly);
  165. return query;
  166. }
  167. /*!
  168. * \brief Returns a reference to the wrapped QSqlDatabase object
  169. */
  170. QSqlDatabase &qSqlDatabase()
  171. {
  172. return m_db;
  173. }
  174. /*!
  175. \brief Executes <em>query</em> and applies function <em>f</em> to each result row.
  176. \param query SQL query string (SELECT statement)
  177. \param f Function (lambda) to apply to
  178. \returns num rows handled with function <em>f</em>
  179. \code
  180. Database db;
  181. db.each("SELECT * FROM table", [](const QueryResult &res)
  182. {
  183. qDebug() << res.toMap();
  184. });
  185. \endcode
  186. */
  187. template<typename Func>
  188. int each (const QString &query, Func&& f) const
  189. {
  190. QueryResult res = execQuery(query);
  191. return Util::each(res, f);
  192. }
  193. /*!
  194. \brief Executes <em>query</em> and applies function <em>f</em> to the first result row.
  195. \param query SQL query string (SELECT statement)
  196. \param f Function (lambda) to apply to
  197. \returns num rows handled with function <em>f</em>
  198. \code
  199. Database db;
  200. db.first("SELECT * FROM table", [](const QueryResult &res)
  201. {
  202. qDebug() << res.toMap();
  203. });
  204. \endcode
  205. */
  206. template<typename Func>
  207. int first (const QString &query, Func&& f) const
  208. {
  209. QueryResult res = execQuery(query);
  210. return Util::first(res, f);
  211. }
  212. /*!
  213. \brief Executes <em>query</em> and applies function <em>f</em> to <em>count</em> result rows starting from index <em>start</em>.
  214. \param query SQL query string (SELECT statement)
  215. \param start Start index
  216. \param count Row count to handle
  217. \param f Function (lambda) to apply to
  218. \returns num rows handled with function <em>f</em>
  219. \code
  220. Database db;
  221. db.range("SELECT * FROM table", 3, 10, [](const QueryResult &res)
  222. {
  223. qDebug() << res.toMap();
  224. });
  225. \endcode
  226. */
  227. template<typename Func>
  228. int range(const QString &query, int start, int count, Func&& f) const
  229. {
  230. QueryResult res = execQuery(query);
  231. return Util::range(res, start, count, f);
  232. }
  233. /*!
  234. \brief Executes <em>query</em> and applies function <em>f</em> to <em>topCount</em> result rows.
  235. \param query SQL query string (SELECT statement)
  236. \param topCount Row count to handle
  237. \param f Function (lambda) to apply to
  238. \returns num rows handled with function <em>f</em>
  239. \code
  240. Database db;
  241. db.top("SELECT * FROM table", 10, [](const QueryResult &res)
  242. {
  243. qDebug() << res.toMap();
  244. });
  245. \endcode
  246. */
  247. template<typename Func>
  248. int top(const QString &query, int topCount, Func&& f) const
  249. {
  250. QueryResult res = execQuery(query);
  251. return Util::top(res, topCount, f);
  252. }
  253. /*!
  254. \brief Executes <em>query</em> and returns scalar value converted to T.
  255. \param query SQL query string (SELECT statement)
  256. \sa QueryResult::scalar
  257. */
  258. template<typename T>
  259. T scalar(const QString &query) const
  260. {
  261. QueryResult res = execQuery(query);
  262. res.next();
  263. return res.scalar<T>();
  264. }
  265. /*!
  266. \brief Executes <em>query</em> and returns scalar value.
  267. \param query SQL query string (SELECT statement)
  268. \sa QueryResult::scalar
  269. */
  270. QVariant scalar(const QString &query) const
  271. {
  272. QueryResult res = execQuery(query);
  273. res.next();
  274. return res.scalar();
  275. }
  276. protected:
  277. QSqlDatabase m_db;
  278. };
  279. /*!
  280. \brief QSqlDatabase transaction wrapper.
  281. Features:
  282. - Automatic rollback of non-expclicitely commited transactions
  283. - Helper methods: Transaction::execNonQuery, Transaction::execQuery, Transaction::insertInto, Transaction::deleteFrom, Transaction::update, Transaction::prepare.
  284. \code
  285. void test()
  286. {
  287. QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
  288. db.setDatabaseName(":memory:");
  289. db.open();
  290. try
  291. {
  292. Transaction t(db);
  293. t.execNonQuery("CREATE TABLE table (a int, b int, c int, d text)");
  294. t.insertInto("table (a, b, c, d)")
  295. .values(1, 2, 3, "row1")
  296. .values(4, 5, 6, "row2")
  297. .values(7, 8, 9, "row3")
  298. .exec();
  299. PreparedQuery query = t.prepare("SELECT a, b, c, d FROM table");
  300. QueryResult res = query.exec();
  301. while (res.next())
  302. {
  303. QVariantMap map = res.toMap();
  304. qDebug() << map;
  305. }
  306. t.update("table")
  307. .set("a", 111)
  308. .set("b", 222)
  309. .where("c = ? OR c = ?", 3, 6);
  310. res = query.exec();
  311. while (res.next())
  312. {
  313. QVariantMap map = res.toMap();
  314. qDebug() << map;
  315. }
  316. t.commit(); //the transaction will be rolled back on exit from the scope (when calling the destructor) if you do not explicitly commit
  317. catch (const DBException &e)
  318. {
  319. //you can handle all the errors at one point
  320. //the transaction will be automatically rolled back on exception
  321. qDebug() << e.lastError << e.lastQuery;
  322. }
  323. }
  324. \endcode
  325. */
  326. class Transaction : public Database
  327. {
  328. Q_DISABLE_COPY(Transaction)
  329. public:
  330. explicit Transaction (const QSqlDatabase &db = QSqlDatabase())
  331. : Database(db)
  332. , m_commited(false)
  333. , m_started(false)
  334. {
  335. m_started = m_db.transaction();
  336. #ifdef DB_EXCEPTIONS_ENABLED
  337. if (!m_started)
  338. {
  339. throw DBException(m_db);
  340. }
  341. #endif
  342. }
  343. Transaction (Transaction&& other)
  344. : Database(std::move(other))
  345. {
  346. m_commited = other.m_commited;
  347. m_started = other.m_started;
  348. other.m_commited = false;
  349. other.m_started = false;
  350. }
  351. Transaction& operator=(Transaction&& other)
  352. {
  353. m_started = other.m_started;
  354. m_commited = other.m_commited;
  355. other.m_commited = false;
  356. other.m_started = false;
  357. return static_cast<Transaction&>(Database::operator=(std::move(other)));
  358. }
  359. ~Transaction()
  360. {
  361. if (m_db.isValid() && !m_commited)
  362. {
  363. m_db.rollback();
  364. }
  365. }
  366. /*!
  367. \brief Commits transaction
  368. The transaction will be rolled back on calling the destructor if not explicitly commited
  369. \throws DBException
  370. */
  371. bool commit()
  372. {
  373. if (m_db.isValid() && !m_commited)
  374. {
  375. m_commited = m_db.commit();
  376. #ifdef DB_EXCEPTIONS_ENABLED
  377. if (!m_commited)
  378. throw DBException(m_db);
  379. #endif
  380. }
  381. return m_commited;
  382. }
  383. /*!
  384. \brief Rolls back transaction
  385. */
  386. bool rollback()
  387. {
  388. bool res = false;
  389. if (m_db.isValid() && !m_commited)
  390. {
  391. res = m_db.rollback();
  392. m_commited = false;
  393. }
  394. return res;
  395. }
  396. /*!
  397. \brief Returns true if the transaction has been started successfully. Otherwise it returns false.
  398. */
  399. bool started() const
  400. {
  401. return m_started;
  402. }
  403. /*!
  404. \brief Returns true if the transaction has been commited successfully. Otherwise it returns false.
  405. */
  406. bool commited() const
  407. {
  408. return m_commited;
  409. }
  410. private:
  411. bool m_commited = false;
  412. bool m_started = false;
  413. };
  414. #endif // EASYQTSQL_TRANSACTION_H