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 @copybrief storages::postgres::Transaction
5
6#include <memory>
7#include <string>
8#include <string_view>
9
10#include <userver/storages/postgres/detail/connection_ptr.hpp>
11#include <userver/storages/postgres/detail/query_parameters.hpp>
12#include <userver/storages/postgres/detail/time_types.hpp>
13#include <userver/storages/postgres/options.hpp>
14#include <userver/storages/postgres/parameter_store.hpp>
15#include <userver/storages/postgres/portal.hpp>
16#include <userver/storages/postgres/postgres_fwd.hpp>
17#include <userver/storages/postgres/query.hpp>
18#include <userver/storages/postgres/result_set.hpp>
19#include <userver/utils/trx_tracker.hpp>
20
21USERVER_NAMESPACE_BEGIN
22
23namespace storages::postgres {
24
25/// @brief PostgreSQL transaction.
26///
27/// RAII wrapper for running transactions on PostgreSQL connections. Should be
28/// retrieved by calling storages::postgres::Cluster::Begin().
29///
30/// Non-copyable.
31///
32/// If the transaction is not explicitly finished (either committed or rolled back)
33/// it will roll itself back in the destructor.
34///
35/// @par Usage synopsis
36/// @code
37/// auto trx = someCluster.Begin(/* transaction options */);
38/// auto res = trx.Execute("select col1, col2 from schema.table");
39/// DoSomething(res);
40/// res = trx.Execute("update schema.table set col1 = $1 where col2 = $2", v1, v2);
41/// // If in the above lines an exception is thrown, then the transaction is
42/// // rolled back in the destructor of trx.
43/// trx.Commit();
44/// @endcode
46public:
47 //@{
48 /** @name Shortcut transaction options constants */
49 /// Read-write read committed transaction
50 static constexpr TransactionOptions RW{}; // NOLINT(readability-identifier-naming)
51 /// Read-only read committed transaction
52 static constexpr TransactionOptions RO{TransactionOptions::kReadOnly}; // NOLINT(readability-identifier-naming)
53 /// Read-only serializable deferrable transaction
54 // clang-format off
55 static constexpr TransactionOptions Deferrable{ // NOLINT(readability-identifier-naming)
57 };
58 /// Read-write repeatable read transaction
59 static constexpr TransactionOptions RepeatableReadRW{ // NOLINT(readability-identifier-naming)
61 };
62 /// Read-write serializable transaction
63 static constexpr TransactionOptions SerializableRW{ // NOLINT(readability-identifier-naming)
65 };
66 /// Read-only repeatable read transaction
67 static constexpr TransactionOptions RepeatableReadRO{ // NOLINT(readability-identifier-naming)
69 TransactionOptions::kReadOnly
70 };
71 /// Read-only serializable transaction
72 static constexpr TransactionOptions SerializableRO{ // NOLINT(readability-identifier-naming)
74 TransactionOptions::kReadOnly
75 };
76 // clang-format on
77 //@}
78
79 static constexpr std::size_t kDefaultRowsInChunk = 1024;
80
81 /// @cond
82 explicit Transaction(
83 detail::ConnectionPtr&& conn,
84 const TransactionOptions& = RW,
85 OptionalCommandControl trx_cmd_ctl = {},
86 detail::SteadyClock::time_point trx_start_time = detail::SteadyClock::now()
87 );
88
89 void SetName(std::string name);
90 /// @endcond
91
92 Transaction(Transaction&&) noexcept;
93 Transaction& operator=(Transaction&&) noexcept;
94
95 Transaction(const Transaction&) = delete;
96 Transaction& operator=(const Transaction&) = delete;
97
98 ~Transaction();
99 /// @name Query execution
100 /// @{
101 /// Execute statement with arbitrary parameters.
102 ///
103 /// Suspends coroutine for execution.
104 ///
105 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
106 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
107 ///
108 /// @snippet storages/postgres/tests/landing_test.cpp TransacExec
109 template <typename... Args>
110 ResultSet Execute(const Query& query, const Args&... args) {
111 return Execute(OptionalCommandControl{}, query, args...);
112 }
113
114 /// Execute statement with arbitrary parameters and per-statement command
115 /// control.
116 ///
117 /// Suspends coroutine for execution.
118 ///
119 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
120 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
121 ///
122 /// @warning Do NOT create a query string manually by embedding arguments!
123 /// It leads to vulnerabilities and bad performance. Either pass arguments
124 /// separately, or use storages::postgres::ParameterScope.
125 template <typename... Args>
126 ResultSet Execute(OptionalCommandControl statement_cmd_ctl, const Query& query, const Args&... args) {
127 detail::StaticQueryParameters<sizeof...(args)> params;
128 params.Write(GetConnectionUserTypes(), args...);
129 return DoExecute(query, detail::QueryParameters{params}, statement_cmd_ctl);
130 }
131
132 /// Execute statement with stored parameters.
133 ///
134 /// Suspends coroutine for execution.
135 ///
136 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
137 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
138 ///
139 /// @warning Do NOT create a query string manually by embedding arguments!
140 /// It leads to vulnerabilities and bad performance. Either pass arguments
141 /// separately, or use storages::postgres::ParameterScope.
142 ResultSet Execute(const Query& query, const ParameterStore& store) {
143 return Execute(OptionalCommandControl{}, query, store);
144 }
145
146 /// Execute statement with stored parameters and per-statement command
147 /// control.
148 ///
149 /// Suspends coroutine for execution.
150 ///
151 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
152 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
153 ///
154 /// @warning Do NOT create a query string manually by embedding arguments!
155 /// It leads to vulnerabilities and bad performance. Either pass arguments
156 /// separately, or use storages::postgres::ParameterScope.
157 ResultSet Execute(OptionalCommandControl statement_cmd_ctl, const Query& query, const ParameterStore& store);
158
159 /// Execute statement that uses an array of arguments transforming that array
160 /// into N arrays of corresponding fields and executing the statement
161 /// with these arrays values.
162 /// Basically, a column-wise Execute.
163 ///
164 /// Useful for statements that unnest their arguments to avoid the need to
165 /// increase timeouts due to data amount growth, but providing an explicit
166 /// mapping from `Container::value_type` to PG type is infeasible for some
167 /// reason (otherwise, use Execute).
168 ///
169 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
170 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
171 ///
172 /// @snippet storages/postgres/tests/arrays_pgtest.cpp ExecuteDecomposeTrx
173 template <typename Container>
174 ResultSet ExecuteDecompose(const Query& query, const Container& args);
175
176 /// Execute statement that uses an array of arguments transforming that array
177 /// into N arrays of corresponding fields and executing the statement
178 /// with these arrays values.
179 /// Basically, a column-wise Execute.
180 ///
181 /// Useful for statements that unnest their arguments to avoid the need to
182 /// increase timeouts due to data amount growth, but providing an explicit
183 /// mapping from `Container::value_type` to PG type is infeasible for some
184 /// reason (otherwise, use Execute).
185 ///
186 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
187 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
188 ///
189 /// @snippet storages/postgres/tests/arrays_pgtest.cpp ExecuteDecomposeTrx
190 template <typename Container>
191 ResultSet ExecuteDecompose(OptionalCommandControl statement_cmd_ctl, const Query& query, const Container& args);
192
193 /// Execute statement that uses an array of arguments splitting that array in
194 /// chunks and executing the statement with a chunk of arguments.
195 ///
196 /// Useful for statements that unnest their arguments to avoid the need to
197 /// increase timeouts due to data amount growth.
198 ///
199 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
200 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
201 template <typename Container>
202 void ExecuteBulk(const Query& query, const Container& args, std::size_t chunk_rows = kDefaultRowsInChunk);
203
204 /// Execute statement that uses an array of arguments splitting that array in
205 /// chunks and executing the statement with a chunk of arguments.
206 ///
207 /// Useful for statements that unnest their arguments to avoid the need to
208 /// increase timeouts due to data amount growth.
209 ///
210 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
211 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
212 template <typename Container>
213 void ExecuteBulk(
214 OptionalCommandControl statement_cmd_ctl,
215 const Query& query,
216 const Container& args,
217 std::size_t chunk_rows = kDefaultRowsInChunk
218 );
219
220 /// Execute statement that uses an array of arguments transforming that array
221 /// into N arrays of corresponding fields and executing the statement
222 /// with a chunk of each of these arrays values.
223 /// Basically, a column-wise ExecuteBulk.
224 ///
225 /// Useful for statements that unnest their arguments to avoid the need to
226 /// increase timeouts due to data amount growth, but providing an explicit
227 /// mapping from `Container::value_type` to PG type is infeasible for some
228 /// reason (otherwise, use ExecuteBulk).
229 ///
230 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
231 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
232 ///
233 /// @snippet storages/postgres/tests/arrays_pgtest.cpp ExecuteDecomposeBulk
234 template <typename Container>
235 void ExecuteDecomposeBulk(const Query& query, const Container& args, std::size_t chunk_rows = kDefaultRowsInChunk);
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 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
248 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
249 ///
250 /// @snippet storages/postgres/tests/arrays_pgtest.cpp ExecuteDecomposeBulk
251 template <typename Container>
253 OptionalCommandControl statement_cmd_ctl,
254 const Query& query,
255 const Container& args,
256 std::size_t chunk_rows = kDefaultRowsInChunk
257 );
258
259 /// @brief Create a portal for fetching results of a statement with arbitrary
260 /// parameters.
261 ///
262 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
263 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
264 template <typename... Args>
265 Portal MakePortal(const Query& query, const Args&... args) {
266 return MakePortal(OptionalCommandControl{}, query, args...);
267 }
268
269 /// @brief Create a portal for fetching results of a statement with arbitrary
270 /// parameters and per-statement command control.
271 ///
272 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
273 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
274 template <typename... Args>
275 Portal MakePortal(OptionalCommandControl statement_cmd_ctl, const Query& query, const Args&... args) {
276 detail::StaticQueryParameters<sizeof...(args)> params;
277 params.Write(GetConnectionUserTypes(), args...);
278 return MakePortal(PortalName{}, query, detail::QueryParameters{params}, statement_cmd_ctl);
279 }
280
281 /// @brief Create a portal for fetching results of a statement with stored
282 /// parameters.
283 ///
284 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
285 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
286 Portal MakePortal(const Query& query, const ParameterStore& store) {
287 return MakePortal(OptionalCommandControl{}, query, store);
288 }
289
290 /// @brief Create a portal for fetching results of a statement with stored parameters
291 /// and per-statement command control.
292 ///
293 /// @note You may write a query in `.sql` file and generate a header file with Query from it.
294 /// See @ref scripts/docs/en/userver/sql_files.md for more information.
295 Portal MakePortal(OptionalCommandControl statement_cmd_ctl, const Query& query, const ParameterStore& store);
296
297 /// Set a connection parameter
298 /// https://www.postgresql.org/docs/current/sql-set.html
299 /// The parameter is set for this transaction only
300 void SetParameter(std::string_view param_name, std::string_view value);
301 //@}
302
303 /// Commit the transaction
304 /// Suspends coroutine until command complete.
305 /// After Commit or Rollback is called, the transaction is not usable any
306 /// more.
307 void Commit();
308 /// Rollback the transaction
309 /// Suspends coroutine until command complete.
310 /// After Commit or Rollback is called, the transaction is not usable any
311 /// more.
312 void Rollback();
313
314 /// Used in tests
316 TimeoutDuration GetConnStatementTimeoutDebug() const;
317
318private:
319 ResultSet DoExecute(
320 const Query& query,
321 const detail::QueryParameters& params,
322 OptionalCommandControl statement_cmd_ctl
323 );
324 Portal MakePortal(
325 const PortalName&,
326 const Query& query,
327 const detail::QueryParameters& params,
328 OptionalCommandControl statement_cmd_ctl
329 );
330
331 const UserTypes& GetConnectionUserTypes() const;
332
333 std::string name_;
334 detail::ConnectionPtr conn_;
335 USERVER_NAMESPACE::utils::trx_tracker::TransactionLock trx_lock_;
336};
337
338template <typename Container>
339ResultSet Transaction::ExecuteDecompose(const Query& query, const Container& args) {
340 return io::DecomposeContainerByColumns(args).Perform([&query, this](const auto&... args) {
341 return this->Execute(query, args...);
342 });
343}
344
345template <typename Container>
347 OptionalCommandControl statement_cmd_ctl,
348 const Query& query,
349 const Container& args
350) {
351 return io::DecomposeContainerByColumns(args).Perform([&query, &statement_cmd_ctl, this](const auto&... args) {
352 return this->Execute(statement_cmd_ctl, query, args...);
353 });
354}
355
356template <typename Container>
357void Transaction::ExecuteBulk(const Query& query, const Container& args, std::size_t chunk_rows) {
358 auto split = io::SplitContainer(args, chunk_rows);
359 for (auto&& chunk : split) {
360 Execute(query, chunk);
361 }
362}
363
364template <typename Container>
366 OptionalCommandControl statement_cmd_ctl,
367 const Query& query,
368 const Container& args,
369 std::size_t chunk_rows
370) {
371 auto split = io::SplitContainer(args, chunk_rows);
372 for (auto&& chunk : split) {
373 Execute(statement_cmd_ctl, query, chunk);
374 }
375}
376
377template <typename Container>
378void Transaction::ExecuteDecomposeBulk(const Query& query, const Container& args, std::size_t chunk_rows) {
379 io::SplitContainerByColumns(args, chunk_rows).Perform([&query, this](const auto&... args) {
380 this->Execute(query, args...);
381 });
382}
383
384template <typename Container>
386 OptionalCommandControl statement_cmd_ctl,
387 const Query& query,
388 const Container& args,
389 std::size_t chunk_rows
390) {
391 io::SplitContainerByColumns(args, chunk_rows).Perform([&query, &statement_cmd_ctl, this](const auto&... args) {
392 this->Execute(statement_cmd_ctl, query, args...);
393 });
394}
395
396} // namespace storages::postgres
397
398USERVER_NAMESPACE_END