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
19USERVER_NAMESPACE_BEGIN
20
21namespace storages::postgres {
22
23/// @page pg_transactions uPg: Transactions
24///
25/// All queries that are run on a PostgreSQL cluster are executed inside
26/// a transaction, even if a single-query interface is used.
27///
28/// A uPg transaction can be started using all isolation levels and modes
29/// supported by PostgreSQL server as specified in documentation here
30/// https://www.postgresql.org/docs/current/static/sql-set-transaction.html.
31/// When starting a transaction, the options are specified using
32/// TransactionOptions structure.
33///
34/// For convenience and improvement of readability there are constants
35/// defined: Transaction::RW, Transaction::RO and Transaction::Deferrable.
36///
37/// @see TransactionOptions
38///
39/// Transaction object ensures that a transaction started in a PostgreSQL
40/// connection will be either committed or rolled back and the connection
41/// will returned back to a connection pool.
42///
43/// @todo Code snippet with transaction starting and committing
44///
45/// Next: @ref pg_run_queries
46///
47/// See also: @ref pg_process_results
48///
49/// ----------
50///
51/// @htmlonly <div class="bottom-nav"> @endhtmlonly
52/// ⇦ @ref pg_driver | @ref pg_run_queries ⇨
53/// @htmlonly </div> @endhtmlonly
54
55/// @page pg_run_queries uPg: Running queries
56///
57/// All queries are executed through a transaction object, event when being
58/// executed through singe-query interface, so here only executing queries
59/// with transaction will be covered. Single-query interface is basically
60/// the same except for additional options.
61///
62/// uPg provides means to execute text queries only. There is no query
63/// generation, but can be used by other tools to execute SQL queries.
64///
65/// @warning A query must contain a single query, multiple statements delimited
66/// by ';' are not supported.
67///
68/// All queries are parsed and prepared during the first invocation and are
69/// executed as prepared statements afterwards.
70///
71/// Any query execution can throw an exception. Please see @ref pg_errors for
72/// more information on possible errors.
73///
74/// @par Queries without parameters
75///
76/// Executing a query without any parameters is rather straightforward.
77/// @code
78/// auto trx = cluster->Begin(/* transaction options */);
79/// auto res = trx.Execute("select foo, bar from foobar");
80/// trx.Commit();
81/// @endcode
82///
83/// The cluster also provides interface for single queries
84/// @code
85/// auto res = cluster->Execute(/* transaction options */, /* the statement */);
86/// @endcode
87///
88/// @par Queries with parameters
89///
90/// uPg supports SQL dollar notation for parameter placeholders. The statement
91/// is prepared at first execution and then only arguments for a query is sent
92/// to the server.
93///
94/// A parameter can be of any type that is supported by the driver. See @ref
95/// pg_types for more information.
96///
97/// @code
98/// auto trx = cluster->Begin(/* transaction options */);
99/// auto res = trx.Execute(
100/// "select foo, bar from foobar where foo > $1 and bar = $2", 42, "baz");
101/// trx.Commit();
102/// @endcode
103///
104/// @see Transaction
105/// @see ResultSet
106///
107/// ----------
108///
109/// @htmlonly <div class="bottom-nav"> @endhtmlonly
110/// ⇦ @ref pg_transactions | @ref pg_process_results ⇨
111/// @htmlonly </div> @endhtmlonly
112
113// clang-format off
114/// @brief PostgreSQL transaction.
115///
116/// RAII wrapper for running transactions on PostgreSQL connections. Should be
117/// retrieved by calling storages::postgres::Cluster::Begin().
118///
119/// Non-copyable.
120///
121/// If the transaction is not explicitly finished (either committed or rolled back)
122/// it will roll itself back in the destructor.
123///
124/// @par Usage synopsis
125/// @code
126/// auto trx = someCluster.Begin(/* transaction options */);
127/// auto res = trx.Execute("select col1, col2 from schema.table");
128/// DoSomething(res);
129/// res = trx.Execute("update schema.table set col1 = $1 where col2 = $2", v1, v2);
130/// // If in the above lines an exception is thrown, then the transaction is
131/// // rolled back in the destructor of trx.
132/// trx.Commit();
133/// @endcode
134// clang-format on
135
137public:
138 //@{
139 /** @name Shortcut transaction options constants */
140 /// Read-write read committed transaction
141 static constexpr TransactionOptions RW{};
142 /// Read-only read committed transaction
143 static constexpr TransactionOptions RO{TransactionOptions::kReadOnly};
144 /// Read-only serializable deferrable transaction
146 //@}
147
148 static constexpr std::size_t kDefaultRowsInChunk = 1024;
149
150 /// @cond
151 explicit Transaction(
152 detail::ConnectionPtr&& conn,
153 const TransactionOptions& = RW,
154 OptionalCommandControl trx_cmd_ctl = {},
155 detail::SteadyClock::time_point trx_start_time = detail::SteadyClock::now()
156 );
157
158 void SetName(std::string name);
159 /// @endcond
160
161 Transaction(Transaction&&) noexcept;
162 Transaction& operator=(Transaction&&) noexcept;
163
164 Transaction(const Transaction&) = delete;
165 Transaction& operator=(const Transaction&) = delete;
166
167 ~Transaction();
168 /// @name Query execution
169 /// @{
170 /// Execute statement with arbitrary parameters.
171 ///
172 /// Suspends coroutine for execution.
173 ///
174 /// @snippet storages/postgres/tests/landing_test.cpp TransacExec
175 template <typename... Args>
176 ResultSet Execute(const Query& query, const Args&... args) {
177 return Execute(OptionalCommandControl{}, query, args...);
178 }
179
180 /// Execute statement with arbitrary parameters and per-statement command
181 /// control.
182 ///
183 /// Suspends coroutine for execution.
184 ///
185 /// @warning Do NOT create a query string manually by embedding arguments!
186 /// It leads to vulnerabilities and bad performance. Either pass arguments
187 /// separately, or use storages::postgres::ParameterScope.
188 template <typename... Args>
189 ResultSet Execute(OptionalCommandControl statement_cmd_ctl, const Query& query, const Args&... args) {
190 detail::StaticQueryParameters<sizeof...(args)> params;
191 params.Write(GetConnectionUserTypes(), args...);
192 return DoExecute(query, detail::QueryParameters{params}, statement_cmd_ctl);
193 }
194
195 /// Execute statement with stored parameters.
196 ///
197 /// Suspends coroutine for execution.
198 ///
199 /// @warning Do NOT create a query string manually by embedding arguments!
200 /// It leads to vulnerabilities and bad performance. Either pass arguments
201 /// separately, or use storages::postgres::ParameterScope.
202 ResultSet Execute(const Query& query, const ParameterStore& store) {
203 return Execute(OptionalCommandControl{}, query, store);
204 }
205
206 /// Execute statement with stored parameters and per-statement command
207 /// control.
208 ///
209 /// Suspends coroutine for execution.
210 ///
211 /// @warning Do NOT create a query string manually by embedding arguments!
212 /// It leads to vulnerabilities and bad performance. Either pass arguments
213 /// separately, or use storages::postgres::ParameterScope.
214 ResultSet Execute(OptionalCommandControl statement_cmd_ctl, const Query& query, const ParameterStore& store);
215
216 /// Execute statement that uses an array of arguments splitting that array in
217 /// chunks and executing the statement with a chunk of arguments.
218 ///
219 /// Useful for statements that unnest their arguments to avoid the need to
220 /// increase timeouts due to data amount growth.
221 template <typename Container>
222 void ExecuteBulk(const Query& query, const Container& args, std::size_t chunk_rows = kDefaultRowsInChunk);
223
224 /// Execute statement that uses an array of arguments splitting that array in
225 /// chunks and executing the statement with a chunk of arguments.
226 ///
227 /// Useful for statements that unnest their arguments to avoid the need to
228 /// increase timeouts due to data amount growth.
229 template <typename Container>
230 void ExecuteBulk(
231 OptionalCommandControl statement_cmd_ctl,
232 const Query& query,
233 const Container& args,
234 std::size_t chunk_rows = kDefaultRowsInChunk
235 );
236
237 /// Execute statement that uses an array of arguments transforming that array
238 /// into N arrays of corresponding fields and executing the statement
239 /// with a chunk of each of these arrays values.
240 /// Basically, a column-wise ExecuteBulk.
241 ///
242 /// Useful for statements that unnest their arguments to avoid the need to
243 /// increase timeouts due to data amount growth, but providing an explicit
244 /// mapping from `Container::value_type` to PG type is infeasible for some
245 /// reason (otherwise, use ExecuteBulk).
246 ///
247 /// @snippet storages/postgres/tests/arrays_pgtest.cpp ExecuteDecomposeBulk
248 template <typename Container>
249 void ExecuteDecomposeBulk(const Query& query, const Container& args, std::size_t chunk_rows = kDefaultRowsInChunk);
250
251 /// Execute statement that uses an array of arguments transforming that array
252 /// into N arrays of corresponding fields and executing the statement
253 /// with a chunk of each of these arrays values.
254 /// Basically, a column-wise ExecuteBulk.
255 ///
256 /// Useful for statements that unnest their arguments to avoid the need to
257 /// increase timeouts due to data amount growth, but providing an explicit
258 /// mapping from `Container::value_type` to PG type is infeasible for some
259 /// reason (otherwise, use ExecuteBulk).
260 ///
261 /// @snippet storages/postgres/tests/arrays_pgtest.cpp ExecuteDecomposeBulk
262 template <typename Container>
264 OptionalCommandControl statement_cmd_ctl,
265 const Query& query,
266 const Container& args,
267 std::size_t chunk_rows = kDefaultRowsInChunk
268 );
269
270 /// Create a portal for fetching results of a statement with arbitrary
271 /// parameters.
272 template <typename... Args>
273 Portal MakePortal(const Query& query, const Args&... args) {
274 return MakePortal(OptionalCommandControl{}, query, args...);
275 }
276
277 /// Create a portal for fetching results of a statement with arbitrary
278 /// parameters and per-statement command control.
279 template <typename... Args>
280 Portal MakePortal(OptionalCommandControl statement_cmd_ctl, const Query& query, const Args&... args) {
281 detail::StaticQueryParameters<sizeof...(args)> params;
282 params.Write(GetConnectionUserTypes(), args...);
283 return MakePortal(PortalName{}, query, detail::QueryParameters{params}, statement_cmd_ctl);
284 }
285
286 /// Create a portal for fetching results of a statement with stored
287 /// parameters.
288 Portal MakePortal(const Query& query, const ParameterStore& store) {
289 return MakePortal(OptionalCommandControl{}, query, store);
290 }
291
292 /// Create a portal for fetching results of a statement with stored parameters
293 /// and per-statement command control.
294 Portal MakePortal(OptionalCommandControl statement_cmd_ctl, const Query& query, const ParameterStore& store);
295
296 /// Set a connection parameter
297 /// https://www.postgresql.org/docs/current/sql-set.html
298 /// The parameter is set for this transaction only
299 void SetParameter(const std::string& param_name, const std::string& value);
300 //@}
301
302 /// Commit the transaction
303 /// Suspends coroutine until command complete.
304 /// After Commit or Rollback is called, the transaction is not usable any
305 /// more.
306 void Commit();
307 /// Rollback the transaction
308 /// Suspends coroutine until command complete.
309 /// After Commit or Rollback is called, the transaction is not usable any
310 /// more.
311 void Rollback();
312
313 /// Used in tests
314 OptionalCommandControl GetConnTransactionCommandControlDebug() const;
315 TimeoutDuration GetConnStatementTimeoutDebug() const;
316
317private:
319 DoExecute(const Query& query, const detail::QueryParameters& params, OptionalCommandControl statement_cmd_ctl);
320 Portal MakePortal(
321 const PortalName&,
322 const Query& query,
323 const detail::QueryParameters& params,
324 OptionalCommandControl statement_cmd_ctl
325 );
326
327 const UserTypes& GetConnectionUserTypes() const;
328
329 std::string name_;
330 detail::ConnectionPtr conn_;
331};
332
333template <typename Container>
334void Transaction::ExecuteBulk(const Query& query, const Container& args, std::size_t chunk_rows) {
335 auto split = io::SplitContainer(args, chunk_rows);
336 for (auto&& chunk : split) {
337 Execute(query, chunk);
338 }
339}
340
341template <typename Container>
343 OptionalCommandControl statement_cmd_ctl,
344 const Query& query,
345 const Container& args,
346 std::size_t chunk_rows
347) {
348 auto split = io::SplitContainer(args, chunk_rows);
349 for (auto&& chunk : split) {
350 Execute(statement_cmd_ctl, query, chunk);
351 }
352}
353
354template <typename Container>
355void Transaction::ExecuteDecomposeBulk(const Query& query, const Container& args, std::size_t chunk_rows) {
356 io::SplitContainerByColumns(args, chunk_rows).Perform([&query, this](const auto&... args) {
357 this->Execute(query, args...);
358 });
359}
360
361template <typename Container>
363 OptionalCommandControl statement_cmd_ctl,
364 const Query& query,
365 const Container& args,
366 std::size_t chunk_rows
367) {
368 io::SplitContainerByColumns(args, chunk_rows).Perform([&query, &statement_cmd_ctl, this](const auto&... args) {
369 this->Execute(statement_cmd_ctl, query, args...);
370 });
371}
372
373} // namespace storages::postgres
374
375USERVER_NAMESPACE_END