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