userver: userver/storages/postgres/transaction.hpp Source File
Loading...
Searching...
No Matches
transaction.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/storages/postgres/transaction.hpp
4/// @brief Transactions
5
6#include <memory>
7#include <string>
8
9#include <userver/storages/postgres/detail/connection_ptr.hpp>
10#include <userver/storages/postgres/detail/query_parameters.hpp>
11#include <userver/storages/postgres/detail/time_types.hpp>
12#include <userver/storages/postgres/options.hpp>
13#include <userver/storages/postgres/parameter_store.hpp>
14#include <userver/storages/postgres/portal.hpp>
15#include <userver/storages/postgres/postgres_fwd.hpp>
16#include <userver/storages/postgres/query.hpp>
17#include <userver/storages/postgres/result_set.hpp>
18#include <userver/utils/trx_tracker.hpp>
19
20USERVER_NAMESPACE_BEGIN
21
22namespace storages::postgres {
23
24/// @page pg_transactions uPg: Transactions
25///
26/// All queries that are run on a PostgreSQL cluster are executed inside
27/// a transaction, even if a single-query interface is used.
28///
29/// A uPg transaction can be started using all isolation levels and modes
30/// supported by PostgreSQL server as specified in documentation here
31/// https://www.postgresql.org/docs/current/static/sql-set-transaction.html.
32/// When starting a transaction, the options are specified using
33/// TransactionOptions structure.
34///
35/// For convenience and improvement of readability there are constants
36/// defined: Transaction::RW, Transaction::RO and Transaction::Deferrable.
37///
38/// @see TransactionOptions
39///
40/// Transaction object ensures that a transaction started in a PostgreSQL
41/// connection will be either committed or rolled back and the connection
42/// will returned back to a connection pool.
43///
44/// @todo Code snippet with transaction starting and committing
45///
46/// Next: @ref pg_run_queries
47///
48/// See also: @ref pg_process_results
49///
50/// ----------
51///
52/// @htmlonly <div class="bottom-nav"> @endhtmlonly
53/// ⇦ @ref pg_driver | @ref pg_run_queries ⇨
54/// @htmlonly </div> @endhtmlonly
55
56/// @page pg_run_queries uPg: Running queries
57///
58/// All queries are executed through a transaction object, event when being
59/// executed through singe-query interface, so here only executing queries
60/// with transaction will be covered. Single-query interface is basically
61/// the same except for additional options.
62///
63/// uPg provides means to execute text queries only. There is no query
64/// generation, but can be used by other tools to execute SQL queries.
65///
66/// @warning A query must contain a single query, multiple statements delimited
67/// by ';' are not supported.
68///
69/// All queries are parsed and prepared during the first invocation and are
70/// executed as prepared statements afterwards.
71///
72/// Any query execution can throw an exception. Please see @ref pg_errors for
73/// more information on possible errors.
74///
75/// @par Queries without parameters
76///
77/// Executing a query without any parameters is rather straightforward.
78/// @code
79/// auto trx = cluster->Begin(/* transaction options */);
80/// auto res = trx.Execute("select foo, bar from foobar");
81/// trx.Commit();
82/// @endcode
83///
84/// The cluster also provides interface for single queries
85/// @code
86/// #include <service/sql_queries.hpp>
87///
88/// auto res = cluster->Execute(/* transaction options */, sql::kMyQuery);
89/// @endcode
90///
91/// You may store SQL queries in separate `.sql` files and access them via
92/// sql_queries.hpp include header. See @ref scripts/docs/en/userver/sql_files.md
93/// for more information.
94///
95/// @par Queries with parameters
96///
97/// uPg supports SQL dollar notation for parameter placeholders. The statement
98/// is prepared at first execution and then only arguments for a query is sent
99/// to the server.
100///
101/// A parameter can be of any type that is supported by the driver.
102/// See @ref scripts/docs/en/userver/pg_types.md for more information.
103///
104/// @code
105/// auto trx = cluster->Begin(/* transaction options */);
106/// auto res = trx.Execute(
107/// "select foo, bar from foobar where foo > $1 and bar = $2", 42, "baz");
108/// trx.Commit();
109/// @endcode
110///
111/// @note You may write a query in `.sql` file and generate a header file with Query from it.
112/// See @ref scripts/docs/en/userver/sql_files.md for more information.
113/// @see Transaction
114/// @see ResultSet
115///
116/// ----------
117///
118/// @htmlonly <div class="bottom-nav"> @endhtmlonly
119/// ⇦ @ref pg_transactions | @ref pg_process_results ⇨
120/// @htmlonly </div> @endhtmlonly
121
122// clang-format off
123/// @brief PostgreSQL transaction.
124///
125/// RAII wrapper for running transactions on PostgreSQL connections. Should be
126/// retrieved by calling storages::postgres::Cluster::Begin().
127///
128/// Non-copyable.
129///
130/// If the transaction is not explicitly finished (either committed or rolled back)
131/// it will roll itself back in the destructor.
132///
133/// @par Usage synopsis
134/// @code
135/// auto trx = someCluster.Begin(/* transaction options */);
136/// auto res = trx.Execute("select col1, col2 from schema.table");
137/// DoSomething(res);
138/// res = trx.Execute("update schema.table set col1 = $1 where col2 = $2", v1, v2);
139/// // If in the above lines an exception is thrown, then the transaction is
140/// // rolled back in the destructor of trx.
141/// trx.Commit();
142/// @endcode
143// clang-format on
144
146public:
147 //@{
148 /** @name Shortcut transaction options constants */
149 /// Read-write read committed transaction
150 static constexpr TransactionOptions RW{}; // NOLINT(readability-identifier-naming)
151 /// Read-only read committed transaction
152 static constexpr TransactionOptions RO{TransactionOptions::kReadOnly}; // NOLINT(readability-identifier-naming)
153 /// Read-only serializable deferrable transaction
154 // clang-format off
155 static constexpr TransactionOptions Deferrable{ // NOLINT(readability-identifier-naming)
157 };
158 /// Read-write repeatable read transaction
159 static constexpr TransactionOptions RepeatableReadRW{ // NOLINT(readability-identifier-naming)
161 };
162 /// Read-write serializable transaction
163 static constexpr TransactionOptions SerializableRW{ // NOLINT(readability-identifier-naming)
165 };
166 /// Read-only repeatable read transaction
167 static constexpr TransactionOptions RepeatableReadRO{ // NOLINT(readability-identifier-naming)
169 TransactionOptions::kReadOnly
170 };
171 /// Read-only serializable transaction
172 static constexpr TransactionOptions SerializableRO{ // NOLINT(readability-identifier-naming)
174 TransactionOptions::kReadOnly
175 };
176 // clang-format on
177 //@}
178
179 static constexpr std::size_t kDefaultRowsInChunk = 1024;
180
181 /// @cond
182 explicit Transaction(
183 detail::ConnectionPtr&& conn,
184 const TransactionOptions& = RW,
185 OptionalCommandControl trx_cmd_ctl = {},
186 detail::SteadyClock::time_point trx_start_time = detail::SteadyClock::now()
187 );
188
189 void SetName(std::string name);
190 /// @endcond
191
192 Transaction(Transaction&&) noexcept;
193 Transaction& operator=(Transaction&&) noexcept;
194
195 Transaction(const Transaction&) = delete;
196 Transaction& operator=(const Transaction&) = delete;
197
198 ~Transaction();
199 /// @name Query execution
200 /// @{
201 /// Execute statement with arbitrary parameters.
202 ///
203 /// Suspends coroutine for execution.
204 ///
205 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
206 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
207 ///
208 /// @snippet storages/postgres/tests/landing_test.cpp TransacExec
209 template <typename... Args>
210 ResultSet Execute(const Query& query, const Args&... args) {
211 return Execute(OptionalCommandControl{}, query, args...);
212 }
213
214 /// Execute statement with arbitrary parameters and per-statement command
215 /// control.
216 ///
217 /// Suspends coroutine for execution.
218 ///
219 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
220 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
221 ///
222 /// @warning Do NOT create a query string manually by embedding arguments!
223 /// It leads to vulnerabilities and bad performance. Either pass arguments
224 /// separately, or use storages::postgres::ParameterScope.
225 template <typename... Args>
226 ResultSet Execute(OptionalCommandControl statement_cmd_ctl, const Query& query, const Args&... args) {
227 detail::StaticQueryParameters<sizeof...(args)> params;
228 params.Write(GetConnectionUserTypes(), args...);
229 return DoExecute(query, detail::QueryParameters{params}, statement_cmd_ctl);
230 }
231
232 /// Execute statement with stored parameters.
233 ///
234 /// Suspends coroutine for execution.
235 ///
236 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
237 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
238 ///
239 /// @warning Do NOT create a query string manually by embedding arguments!
240 /// It leads to vulnerabilities and bad performance. Either pass arguments
241 /// separately, or use storages::postgres::ParameterScope.
242 ResultSet Execute(const Query& query, const ParameterStore& store) {
243 return Execute(OptionalCommandControl{}, query, store);
244 }
245
246 /// Execute statement with stored parameters and per-statement command
247 /// control.
248 ///
249 /// Suspends coroutine for execution.
250 ///
251 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
252 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
253 ///
254 /// @warning Do NOT create a query string manually by embedding arguments!
255 /// It leads to vulnerabilities and bad performance. Either pass arguments
256 /// separately, or use storages::postgres::ParameterScope.
257 ResultSet Execute(OptionalCommandControl statement_cmd_ctl, const Query& query, const ParameterStore& store);
258
259 /// Execute statement that uses an array of arguments transforming that array
260 /// into N arrays of corresponding fields and executing the statement
261 /// with these arrays values.
262 /// Basically, a column-wise Execute.
263 ///
264 /// Useful for statements that unnest their arguments to avoid the need to
265 /// increase timeouts due to data amount growth, but providing an explicit
266 /// mapping from `Container::value_type` to PG type is infeasible for some
267 /// reason (otherwise, use Execute).
268 ///
269 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
270 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
271 ///
272 /// @snippet storages/postgres/tests/arrays_pgtest.cpp ExecuteDecomposeTrx
273 template <typename Container>
274 ResultSet ExecuteDecompose(const Query& query, const Container& args);
275
276 /// Execute statement that uses an array of arguments transforming that array
277 /// into N arrays of corresponding fields and executing the statement
278 /// with these arrays values.
279 /// Basically, a column-wise Execute.
280 ///
281 /// Useful for statements that unnest their arguments to avoid the need to
282 /// increase timeouts due to data amount growth, but providing an explicit
283 /// mapping from `Container::value_type` to PG type is infeasible for some
284 /// reason (otherwise, use Execute).
285 ///
286 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
287 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
288 ///
289 /// @snippet storages/postgres/tests/arrays_pgtest.cpp ExecuteDecomposeTrx
290 template <typename Container>
291 ResultSet ExecuteDecompose(OptionalCommandControl statement_cmd_ctl, const Query& query, const Container& args);
292
293 /// Execute statement that uses an array of arguments splitting that array in
294 /// chunks and executing the statement with a chunk of arguments.
295 ///
296 /// Useful for statements that unnest their arguments to avoid the need to
297 /// increase timeouts due to data amount growth.
298 ///
299 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
300 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
301 template <typename Container>
302 void ExecuteBulk(const Query& query, const Container& args, std::size_t chunk_rows = kDefaultRowsInChunk);
303
304 /// Execute statement that uses an array of arguments splitting that array in
305 /// chunks and executing the statement with a chunk of arguments.
306 ///
307 /// Useful for statements that unnest their arguments to avoid the need to
308 /// increase timeouts due to data amount growth.
309 ///
310 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
311 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
312 template <typename Container>
313 void ExecuteBulk(
314 OptionalCommandControl statement_cmd_ctl,
315 const Query& query,
316 const Container& args,
317 std::size_t chunk_rows = kDefaultRowsInChunk
318 );
319
320 /// Execute statement that uses an array of arguments transforming that array
321 /// into N arrays of corresponding fields and executing the statement
322 /// with a chunk of each of these arrays values.
323 /// Basically, a column-wise ExecuteBulk.
324 ///
325 /// Useful for statements that unnest their arguments to avoid the need to
326 /// increase timeouts due to data amount growth, but providing an explicit
327 /// mapping from `Container::value_type` to PG type is infeasible for some
328 /// reason (otherwise, use ExecuteBulk).
329 ///
330 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
331 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
332 ///
333 /// @snippet storages/postgres/tests/arrays_pgtest.cpp ExecuteDecomposeBulk
334 template <typename Container>
335 void ExecuteDecomposeBulk(const Query& query, const Container& args, std::size_t chunk_rows = kDefaultRowsInChunk);
336
337 /// Execute statement that uses an array of arguments transforming that array
338 /// into N arrays of corresponding fields and executing the statement
339 /// with a chunk of each of these arrays values.
340 /// Basically, a column-wise ExecuteBulk.
341 ///
342 /// Useful for statements that unnest their arguments to avoid the need to
343 /// increase timeouts due to data amount growth, but providing an explicit
344 /// mapping from `Container::value_type` to PG type is infeasible for some
345 /// reason (otherwise, use ExecuteBulk).
346 ///
347 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
348 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
349 ///
350 /// @snippet storages/postgres/tests/arrays_pgtest.cpp ExecuteDecomposeBulk
351 template <typename Container>
353 OptionalCommandControl statement_cmd_ctl,
354 const Query& query,
355 const Container& args,
356 std::size_t chunk_rows = kDefaultRowsInChunk
357 );
358
359 /// @brief Create a portal for fetching results of a statement with arbitrary
360 /// parameters.
361 ///
362 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
363 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
364 template <typename... Args>
365 Portal MakePortal(const Query& query, const Args&... args) {
366 return MakePortal(OptionalCommandControl{}, query, args...);
367 }
368
369 /// @brief Create a portal for fetching results of a statement with arbitrary
370 /// parameters and per-statement command control.
371 ///
372 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
373 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
374 template <typename... Args>
375 Portal MakePortal(OptionalCommandControl statement_cmd_ctl, const Query& query, const Args&... args) {
376 detail::StaticQueryParameters<sizeof...(args)> params;
377 params.Write(GetConnectionUserTypes(), args...);
378 return MakePortal(PortalName{}, query, detail::QueryParameters{params}, statement_cmd_ctl);
379 }
380
381 /// @brief Create a portal for fetching results of a statement with stored
382 /// parameters.
383 ///
384 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
385 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
386 Portal MakePortal(const Query& query, const ParameterStore& store) {
387 return MakePortal(OptionalCommandControl{}, query, store);
388 }
389
390 /// @brief Create a portal for fetching results of a statement with stored parameters
391 /// and per-statement command control.
392 ///
393 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
394 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
395 Portal MakePortal(OptionalCommandControl statement_cmd_ctl, const Query& query, const ParameterStore& store);
396
397 /// Set a connection parameter
398 /// https://www.postgresql.org/docs/current/sql-set.html
399 /// The parameter is set for this transaction only
400 void SetParameter(const std::string& param_name, const std::string& value);
401 //@}
402
403 /// Commit the transaction
404 /// Suspends coroutine until command complete.
405 /// After Commit or Rollback is called, the transaction is not usable any
406 /// more.
407 void Commit();
408 /// Rollback the transaction
409 /// Suspends coroutine until command complete.
410 /// After Commit or Rollback is called, the transaction is not usable any
411 /// more.
412 void Rollback();
413
414 /// Used in tests
415 OptionalCommandControl GetConnTransactionCommandControlDebug() const;
416 TimeoutDuration GetConnStatementTimeoutDebug() const;
417
418private:
419 ResultSet DoExecute(
420 const Query& query,
421 const detail::QueryParameters& params,
422 OptionalCommandControl statement_cmd_ctl
423 );
424 Portal MakePortal(
425 const PortalName&,
426 const Query& query,
427 const detail::QueryParameters& params,
428 OptionalCommandControl statement_cmd_ctl
429 );
430
431 const UserTypes& GetConnectionUserTypes() const;
432
433 std::string name_;
434 detail::ConnectionPtr conn_;
435 USERVER_NAMESPACE::utils::trx_tracker::TransactionLock trx_lock_;
436};
437
438template <typename Container>
439ResultSet Transaction::ExecuteDecompose(const Query& query, const Container& args) {
440 return io::DecomposeContainerByColumns(args).Perform([&query, this](const auto&... args) {
441 return this->Execute(query, args...);
442 });
443}
444
445template <typename Container>
447 OptionalCommandControl statement_cmd_ctl,
448 const Query& query,
449 const Container& args
450) {
451 return io::DecomposeContainerByColumns(args).Perform([&query, &statement_cmd_ctl, this](const auto&... args) {
452 return this->Execute(statement_cmd_ctl, query, args...);
453 });
454}
455
456template <typename Container>
457void Transaction::ExecuteBulk(const Query& query, const Container& args, std::size_t chunk_rows) {
458 auto split = io::SplitContainer(args, chunk_rows);
459 for (auto&& chunk : split) {
460 Execute(query, chunk);
461 }
462}
463
464template <typename Container>
466 OptionalCommandControl statement_cmd_ctl,
467 const Query& query,
468 const Container& args,
469 std::size_t chunk_rows
470) {
471 auto split = io::SplitContainer(args, chunk_rows);
472 for (auto&& chunk : split) {
473 Execute(statement_cmd_ctl, query, chunk);
474 }
475}
476
477template <typename Container>
478void Transaction::ExecuteDecomposeBulk(const Query& query, const Container& args, std::size_t chunk_rows) {
479 io::SplitContainerByColumns(args, chunk_rows).Perform([&query, this](const auto&... args) {
480 this->Execute(query, args...);
481 });
482}
483
484template <typename Container>
486 OptionalCommandControl statement_cmd_ctl,
487 const Query& query,
488 const Container& args,
489 std::size_t chunk_rows
490) {
491 io::SplitContainerByColumns(args, chunk_rows).Perform([&query, &statement_cmd_ctl, this](const auto&... args) {
492 this->Execute(statement_cmd_ctl, query, args...);
493 });
494}
495
496} // namespace storages::postgres
497
498USERVER_NAMESPACE_END