pipeline.hpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. //
  2. // Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
  3. //
  4. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  5. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. //
  7. #ifndef BOOST_MYSQL_PIPELINE_HPP
  8. #define BOOST_MYSQL_PIPELINE_HPP
  9. #include <boost/mysql/character_set.hpp>
  10. #include <boost/mysql/diagnostics.hpp>
  11. #include <boost/mysql/error_code.hpp>
  12. #include <boost/mysql/field_view.hpp>
  13. #include <boost/mysql/results.hpp>
  14. #include <boost/mysql/statement.hpp>
  15. #include <boost/mysql/string_view.hpp>
  16. #include <boost/mysql/detail/access.hpp>
  17. #include <boost/mysql/detail/config.hpp>
  18. #include <boost/mysql/detail/execution_processor/execution_processor.hpp>
  19. #include <boost/mysql/detail/pipeline.hpp>
  20. #include <boost/mysql/detail/writable_field_traits.hpp>
  21. #include <boost/assert.hpp>
  22. #include <boost/core/span.hpp>
  23. #include <boost/variant2/variant.hpp>
  24. #include <array>
  25. #include <cstdint>
  26. #include <utility>
  27. #include <vector>
  28. namespace boost {
  29. namespace mysql {
  30. /**
  31. * \brief (EXPERIMENTAL) A variant-like type holding the response of a single pipeline stage.
  32. * \details
  33. * This is a variant-like type, similar to `boost::system::result`. At any point in time,
  34. * it can contain: \n
  35. * \li A \ref statement. Will happen if the stage was a prepare statement that succeeded.
  36. * \li A \ref results. Will happen if the stage was a query or statement execution that succeeded.
  37. * \li An \ref error_code, \ref diagnostics pair. Will happen if the stage failed, or if it succeeded but
  38. * it doesn't yield a value (as in close statement, reset connection and set character set).
  39. *
  40. * \par Experimental
  41. * This part of the API is experimental, and may change in successive
  42. * releases without previous notice.
  43. */
  44. class stage_response
  45. {
  46. #ifndef BOOST_MYSQL_DOXYGEN
  47. struct errcode_with_diagnostics
  48. {
  49. error_code ec;
  50. diagnostics diag;
  51. };
  52. struct
  53. {
  54. variant2::variant<errcode_with_diagnostics, statement, results> value;
  55. void emplace_results() { value.emplace<results>(); }
  56. void emplace_error() { value.emplace<errcode_with_diagnostics>(); }
  57. detail::execution_processor& get_processor()
  58. {
  59. return detail::access::get_impl(variant2::unsafe_get<2>(value));
  60. }
  61. void set_result(statement s) { value = s; }
  62. void set_error(error_code ec, diagnostics&& diag)
  63. {
  64. value.emplace<0>(errcode_with_diagnostics{ec, std::move(diag)});
  65. }
  66. } impl_;
  67. friend struct detail::access;
  68. #endif
  69. bool has_error() const { return impl_.value.index() == 0u; }
  70. BOOST_MYSQL_DECL
  71. void check_has_results() const;
  72. public:
  73. /**
  74. * \brief Default constructor.
  75. * \details
  76. * Constructs an object containing an empty error code and diagnostics.
  77. *
  78. * \par Exception safety
  79. * No-throw guarantee.
  80. */
  81. stage_response() = default;
  82. /**
  83. * \brief Returns true if the object contains a statement.
  84. *
  85. * \par Exception safety
  86. * No-throw guarantee.
  87. */
  88. bool has_statement() const noexcept { return impl_.value.index() == 1u; }
  89. /**
  90. * \brief Returns true if the object contains a results.
  91. *
  92. * \par Exception safety
  93. * No-throw guarantee.
  94. */
  95. bool has_results() const noexcept { return impl_.value.index() == 2u; }
  96. /**
  97. * \brief Retrieves the contained error code.
  98. * \details
  99. * If `*this` contains an error, retrieves it.
  100. * Otherwise (if `this->has_statement() || this->has_results()`),
  101. * returns an empty (default-constructed) error code.
  102. *
  103. * \par Exception safety
  104. * No-throw guarantee.
  105. */
  106. error_code error() const noexcept
  107. {
  108. return has_error() ? variant2::unsafe_get<0>(impl_.value).ec : error_code();
  109. }
  110. /**
  111. * \brief Retrieves the contained diagnostics (lvalue reference accessor).
  112. * \details
  113. * If `*this` contains an error, retrieves the associated diagnostic information
  114. * by copying it.
  115. * Otherwise (if `this->has_statement() || this->has_results()`),
  116. * returns an empty diagnostics object.
  117. *
  118. * \par Exception safety
  119. * Strong guarantee: memory allocations may throw.
  120. */
  121. diagnostics diag() const&
  122. {
  123. return has_error() ? variant2::unsafe_get<0>(impl_.value).diag : diagnostics();
  124. }
  125. /**
  126. * \brief Retrieves the contained diagnostics (rvalue reference accessor).
  127. * \details
  128. * If `*this` contains an error, retrieves the associated diagnostic information
  129. * by moving it.
  130. * Otherwise (if `this->has_statement() || this->has_results()`),
  131. * returns an empty (default-constructed) error.
  132. *
  133. * \par Exception safety
  134. * No-throw guarantee.
  135. */
  136. diagnostics diag() && noexcept
  137. {
  138. return has_error() ? variant2::unsafe_get<0>(std::move(impl_.value)).diag : diagnostics();
  139. }
  140. /**
  141. * \brief Retrieves the contained statement or throws an exception.
  142. * \details
  143. * If `*this` contains an statement (`this->has_statement() == true`),
  144. * retrieves it. Otherwise, throws an exception.
  145. *
  146. * \par Exception safety
  147. * Strong guarantee. Throws on invalid input.
  148. * \throws std::invalid_argument If `*this` does not contain a statement.
  149. */
  150. BOOST_MYSQL_DECL
  151. statement as_statement() const;
  152. /**
  153. * \brief Retrieves the contained statement (unchecked accessor).
  154. * \details
  155. * If `*this` contains an statement,
  156. * retrieves it. Otherwise, the behavior is undefined.
  157. *
  158. * \par Preconditions
  159. * `this->has_statement() == true`
  160. *
  161. * \par Exception safety
  162. * No-throw guarantee.
  163. */
  164. statement get_statement() const noexcept
  165. {
  166. BOOST_ASSERT(has_statement());
  167. return variant2::unsafe_get<1>(impl_.value);
  168. }
  169. /**
  170. * \brief Retrieves the contained results or throws an exception.
  171. * \details
  172. * If `*this` contains a `results` object (`this->has_results() == true`),
  173. * retrieves a reference to it. Otherwise, throws an exception.
  174. *
  175. * \par Exception safety
  176. * Strong guarantee. Throws on invalid input.
  177. * \throws std::invalid_argument If `this->has_results() == false`
  178. *
  179. * \par Object lifetimes
  180. * The returned reference is valid as long as `*this` is alive
  181. * and hasn't been assigned to.
  182. */
  183. const results& as_results() const&
  184. {
  185. check_has_results();
  186. return variant2::unsafe_get<2>(impl_.value);
  187. }
  188. /// \copydoc as_results
  189. results&& as_results() &&
  190. {
  191. check_has_results();
  192. return variant2::unsafe_get<2>(std::move(impl_.value));
  193. }
  194. /**
  195. * \brief Retrieves the contained results (unchecked accessor).
  196. * \details
  197. * If `*this` contains a `results` object, retrieves a reference to it.
  198. * Otherwise, the behavior is undefined.
  199. *
  200. * \par Preconditions
  201. * `this->has_results() == true`
  202. *
  203. * \par Exception safety
  204. * No-throw guarantee.
  205. *
  206. * \par Object lifetimes
  207. * The returned reference is valid as long as `*this` is alive
  208. * and hasn't been assigned to.
  209. */
  210. const results& get_results() const& noexcept
  211. {
  212. BOOST_ASSERT(has_results());
  213. return variant2::unsafe_get<2>(impl_.value);
  214. }
  215. /// \copydoc get_results
  216. results&& get_results() && noexcept
  217. {
  218. BOOST_ASSERT(has_results());
  219. return variant2::unsafe_get<2>(std::move(impl_.value));
  220. }
  221. };
  222. /**
  223. * \brief (EXPERIMENTAL) A pipeline request.
  224. * \details
  225. * Contains a collection of pipeline stages, fully describing the work to be performed
  226. * by a pipeline operation.
  227. * Call any of the `add_xxx` functions to append new stages to the request.
  228. *
  229. * \par Experimental
  230. * This part of the API is experimental, and may change in successive
  231. * releases without previous notice.
  232. */
  233. class pipeline_request
  234. {
  235. #ifndef BOOST_MYSQL_DOXYGEN
  236. struct impl_t
  237. {
  238. std::vector<std::uint8_t> buffer_;
  239. std::vector<detail::pipeline_request_stage> stages_;
  240. } impl_;
  241. friend struct detail::access;
  242. #endif
  243. public:
  244. /**
  245. * \brief Default constructor.
  246. * \details Constructs an empty pipeline request, with no stages.
  247. *
  248. * \par Exception safety
  249. * No-throw guarantee.
  250. */
  251. pipeline_request() = default;
  252. /**
  253. * \brief Adds a stage that executes a text query.
  254. * \details
  255. * Creates a stage that will run `query` as a SQL query,
  256. * like \ref any_connection::execute.
  257. *
  258. * \par Exception safety
  259. * Strong guarantee. Memory allocations may throw.
  260. *
  261. * \par Object lifetimes
  262. * query is copied into the request and need not be kept alive after this function returns.
  263. */
  264. BOOST_MYSQL_DECL
  265. pipeline_request& add_execute(string_view query);
  266. /**
  267. * \brief Adds a stage that executes a prepared statement.
  268. * \details
  269. * Creates a stage that runs
  270. * `stmt` bound to any parameters passed in `params`, like \ref any_connection::execute
  271. * and \ref statement::bind. For example, `add_execute(stmt, 42, "John")` has
  272. * effects equivalent to `conn.execute(stmt.bind(42, "John"))`.
  273. *
  274. * \par Exception safety
  275. * Strong guarantee. Throws if the supplied number of parameters doesn't match the number
  276. * of parameters expected by the statement. Additionally, memory allocations may throw.
  277. * \throws std::invalid_argument If `sizeof...(params) != stmt.num_params()`
  278. *
  279. * \par Preconditions
  280. * The passed statement should be valid (`stmt.valid() == true`).
  281. *
  282. * \par Object lifetimes
  283. * Any objects pointed to by `params` are copied into the request and
  284. * need not be kept alive after this function returns.
  285. *
  286. * \par Type requirements
  287. * Any type satisfying `WritableField` can be used as a parameter.
  288. * This includes all types that can be used with \ref statement::bind,
  289. * including scalar types, strings, blobs and optionals.
  290. */
  291. template <BOOST_MYSQL_WRITABLE_FIELD... WritableField>
  292. pipeline_request& add_execute(statement stmt, const WritableField&... params)
  293. {
  294. std::array<field_view, sizeof...(WritableField)> params_arr{{detail::to_field(params)...}};
  295. return add_execute_range(stmt, params_arr);
  296. }
  297. /**
  298. * \brief Adds a stage that executes a prepared statement.
  299. * \details
  300. * Creates a stage that runs
  301. * `stmt` bound to any parameters passed in `params`, like \ref any_connection::execute
  302. * and \ref statement::bind. For example, `add_execute_range(stmt, params)` has
  303. * effects equivalent to `conn.execute(stmt.bind(params.begin(), params.end()))`.
  304. * \n
  305. * This function can be used instead of \ref add_execute when the number of actual parameters
  306. * of a statement is not known at compile time.
  307. *
  308. * \par Exception safety
  309. * Strong guarantee. Throws if the supplied number of parameters doesn't match the number
  310. * of parameters expected by the statement. Additionally, memory allocations may throw.
  311. * \throws std::invalid_argument If `params.size() != stmt.num_params()`
  312. *
  313. * \par Preconditions
  314. * The passed statement should be valid (`stmt.valid() == true`).
  315. *
  316. * \par Object lifetimes
  317. * The `params` range is copied into the request and
  318. * needs not be kept alive after this function returns.
  319. */
  320. BOOST_MYSQL_DECL
  321. pipeline_request& add_execute_range(statement stmt, span<const field_view> params);
  322. /**
  323. * \brief Adds a prepare statement stage.
  324. * \details
  325. * Creates a stage that prepares a statement server-side. The resulting
  326. * stage has effects equivalent to `conn.prepare_statement(stmt_sql)`.
  327. *
  328. * \par Exception safety
  329. * Strong guarantee. Memory allocations may throw.
  330. *
  331. * \par Object lifetimes
  332. * stmt_sql is copied into the request and need not be kept alive after this function returns.
  333. */
  334. BOOST_MYSQL_DECL
  335. pipeline_request& add_prepare_statement(string_view stmt_sql);
  336. /**
  337. * \brief Adds a close statement stage.
  338. * \details
  339. * Creates a stage that closes a prepared statement. The resulting
  340. * stage has effects equivalent to `conn.close_statement(stmt)`.
  341. *
  342. * \par Exception safety
  343. * Strong guarantee. Memory allocations may throw.
  344. *
  345. * \par Preconditions
  346. * The passed statement should be valid (`stmt.valid() == true`).
  347. */
  348. BOOST_MYSQL_DECL pipeline_request& add_close_statement(statement stmt);
  349. /**
  350. * \brief Adds a reset connection stage.
  351. * \details
  352. * Creates a stage that resets server-side session state. The resulting
  353. * stage has effects equivalent to `conn.reset_connection()`.
  354. *
  355. * \par Exception safety
  356. * Strong guarantee. Memory allocations may throw.
  357. */
  358. BOOST_MYSQL_DECL pipeline_request& add_reset_connection();
  359. /**
  360. * \brief Adds a set character set stage.
  361. * \details
  362. * Creates a stage that sets the connection's character set.
  363. * The resulting stage has effects equivalent to `conn.set_character_set(charset)`.
  364. *
  365. * \par Exception safety
  366. * Strong guarantee. Throws if the supplied character set name is not valid
  367. * (i.e. `charset.name` contains non-ASCII characters).
  368. * The check is performed as a hardening measure, and never happens with
  369. * the character sets provided by this library.
  370. *
  371. * Additionally, memory allocations may throw.
  372. * \throws std::invalid_argument If `charset.name` contains non-ASCII characters.
  373. *
  374. * \par Preconditions
  375. * The passed character set should not be default-constructed
  376. * (`charset.name != nullptr`).
  377. */
  378. BOOST_MYSQL_DECL pipeline_request& add_set_character_set(character_set charset);
  379. /**
  380. * \brief Removes all stages in the pipeline request, making the object empty again.
  381. * \details
  382. * Can be used to re-use a single request object for multiple pipeline operations.
  383. *
  384. * \par Exception safety
  385. * No-throw guarantee.
  386. */
  387. void clear() noexcept
  388. {
  389. impl_.buffer_.clear();
  390. impl_.stages_.clear();
  391. }
  392. };
  393. } // namespace mysql
  394. } // namespace boost
  395. #ifdef BOOST_MYSQL_HEADER_ONLY
  396. #include <boost/mysql/impl/pipeline.ipp>
  397. #endif
  398. #endif