1 /** Common code and definitions for the transaction classes.
2 *
3 * pqxx::transaction_base defines the interface for any abstract class that
4 * represents a database transaction.
5 *
6 * Copyright (c) 2000-2020, Jeroen T. Vermeulen.
7 *
8 * See COPYING for copyright license. If you did not receive a file called
9 * COPYING with this source code, please notify the distributor of this
10 * mistake, or contact the author.
11 */
12 #include "pqxx-source.hxx"
13
14 #include <cstring>
15 #include <stdexcept>
16
17 #include "pqxx/connection"
18 #include "pqxx/result"
19 #include "pqxx/transaction_base"
20
21 #include "pqxx/internal/gates/connection-transaction.hxx"
22 #include "pqxx/internal/gates/transaction-transactionfocus.hxx"
23
24 #include "pqxx/internal/encodings.hxx"
25
26
transaction_base(connection & c)27 pqxx::transaction_base::transaction_base(connection &c) :
28 namedclass{"transaction_base"}, m_conn{c}
29 {}
30
31
~transaction_base()32 pqxx::transaction_base::~transaction_base()
33 {
34 try
35 {
36 if (not std::empty(m_pending_error))
37 process_notice("UNPROCESSED ERROR: " + m_pending_error + "\n");
38
39 if (m_registered)
40 {
41 m_conn.process_notice(description() + " was never closed properly!\n");
42 pqxx::internal::gate::connection_transaction{conn()}
43 .unregister_transaction(this);
44 }
45 }
46 catch (std::exception const &e)
47 {
48 try
49 {
50 process_notice(std::string{e.what()} + "\n");
51 }
52 catch (std::exception const &)
53 {
54 process_notice(e.what());
55 }
56 }
57 }
58
59
register_transaction()60 void pqxx::transaction_base::register_transaction()
61 {
62 pqxx::internal::gate::connection_transaction{conn()}.register_transaction(
63 this);
64 m_registered = true;
65 }
66
67
commit()68 void pqxx::transaction_base::commit()
69 {
70 check_pending_error();
71
72 // Check previous status code. Caller should only call this function if
73 // we're in "implicit" state, but multiple commits are silently accepted.
74 switch (m_status)
75 {
76 case status::nascent: // We never managed to start the transaction.
77 throw usage_error{
78 "Attempt to commit unserviceable " + description() + "."};
79 return;
80
81 case status::active: // Just fine. This is what we expect.
82 break;
83
84 case status::aborted:
85 throw usage_error{"Attempt to commit previously aborted " + description()};
86
87 case status::committed:
88 // Transaction has been committed already. This is not exactly proper
89 // behaviour, but throwing an exception here would only give the impression
90 // that an abort is needed--which would only confuse things further at this
91 // stage.
92 // Therefore, multiple commits are accepted, though under protest.
93 m_conn.process_notice(description() + " committed more than once.\n");
94 return;
95
96 case status::in_doubt:
97 // Transaction may or may not have been committed. The only thing we can
98 // really do is keep telling the caller that the transaction is in doubt.
99 throw in_doubt_error{
100 description() + " committed again while in an indeterminate state."};
101
102 default: throw internal_error{"pqxx::transaction: invalid status code."};
103 }
104
105 // Tricky one. If stream is nested in transaction but inside the same scope,
106 // the commit() will come before the stream is closed. Which means the
107 // commit is premature. Punish this swiftly and without fail to discourage
108 // the habit from forming.
109 if (m_focus.get() != nullptr)
110 throw failure{
111 "Attempt to commit " + description() + " with " +
112 m_focus.get()->description() + " still open."};
113
114 // Check that we're still connected (as far as we know--this is not an
115 // absolute thing!) before trying to commit. If the connection was broken
116 // already, the commit would fail anyway but this way at least we don't
117 // remain in-doubt as to whether the backend got the commit order at all.
118 if (not m_conn.is_open())
119 throw broken_connection{
120 "Broken connection to backend; cannot complete transaction."};
121
122 try
123 {
124 do_commit();
125 m_status = status::committed;
126 }
127 catch (in_doubt_error const &)
128 {
129 m_status = status::in_doubt;
130 throw;
131 }
132 catch (std::exception const &)
133 {
134 m_status = status::aborted;
135 throw;
136 }
137
138 close();
139 }
140
141
abort()142 void pqxx::transaction_base::abort()
143 {
144 // Check previous status code. Quietly accept multiple aborts to
145 // simplify emergency bailout code.
146 switch (m_status)
147 {
148 case status::nascent: // Never began transaction. No need to issue rollback.
149 return;
150
151 case status::active:
152 try
153 {
154 do_abort();
155 }
156 catch (std::exception const &)
157 {}
158 break;
159
160 case status::aborted: return;
161
162 case status::committed:
163 throw usage_error{
164 "Attempt to abort previously committed " + description()};
165
166 case status::in_doubt:
167 // Aborting an in-doubt transaction is probably a reasonably sane response
168 // to an insane situation. Log it, but do not complain.
169 m_conn.process_notice(
170 "Warning: " + description() +
171 " aborted after going into "
172 "indeterminate state; it may have been executed anyway.\n");
173 return;
174
175 default: throw internal_error{"Invalid transaction status."};
176 }
177
178 m_status = status::aborted;
179 close();
180 }
181
182
esc_raw(std::string const & bin) const183 std::string pqxx::transaction_base::esc_raw(std::string const &bin) const
184 {
185 auto const p{reinterpret_cast<unsigned char const *>(bin.c_str())};
186 return conn().esc_raw(p, std::size(bin));
187 }
188
189
quote_raw(std::string const & bin) const190 std::string pqxx::transaction_base::quote_raw(std::string const &bin) const
191 {
192 auto const p{reinterpret_cast<unsigned char const *>(bin.c_str())};
193 return conn().quote_raw(p, std::size(bin));
194 }
195
196
197 pqxx::result
exec(std::string_view query,std::string const & desc)198 pqxx::transaction_base::exec(std::string_view query, std::string const &desc)
199 {
200 check_pending_error();
201
202 std::string const n{std::empty(desc) ? "" : "'" + desc + "' "};
203
204 if (m_focus.get() != nullptr)
205 throw usage_error{
206 "Attempt to execute query " + n + "on " + description() +
207 " "
208 "with " +
209 m_focus.get()->description() + " still open."};
210
211
212 switch (m_status)
213 {
214 case status::nascent:
215 throw usage_error{
216 "Could not execute query " + n +
217 ": "
218 "transaction startup failed."};
219
220 case status::active: break;
221
222 case status::committed:
223 case status::aborted:
224 case status::in_doubt:
225 throw usage_error{
226 "Could not execute query " + n +
227 ": "
228 "transaction is already closed."};
229
230 default: throw internal_error{"pqxx::transaction: invalid status code."};
231 }
232
233 // TODO: Pass desc to direct_exec(), and from there on down.
234 return direct_exec(query);
235 }
236
237
exec_n(result::size_type rows,std::string const & query,std::string const & desc)238 pqxx::result pqxx::transaction_base::exec_n(
239 result::size_type rows, std::string const &query, std::string const &desc)
240 {
241 result const r{exec(query, desc)};
242 if (std::size(r) != rows)
243 {
244 std::string const N{std::empty(desc) ? "" : "'" + desc + "'"};
245 throw unexpected_rows{
246 "Expected " + to_string(rows) +
247 " row(s) of data "
248 "from query " +
249 N + ", got " + to_string(std::size(r)) + "."};
250 }
251 return r;
252 }
253
254
check_rowcount_prepared(zview statement,result::size_type expected_rows,result::size_type actual_rows)255 void pqxx::transaction_base::check_rowcount_prepared(
256 zview statement, result::size_type expected_rows,
257 result::size_type actual_rows)
258 {
259 if (actual_rows != expected_rows)
260 {
261 throw unexpected_rows{
262 "Expected " + to_string(expected_rows) +
263 " row(s) of data "
264 "from prepared statement '" +
265 std::string{statement} + "', got " + to_string(actual_rows) + "."};
266 }
267 }
268
269
check_rowcount_params(std::size_t expected_rows,std::size_t actual_rows)270 void pqxx::transaction_base::check_rowcount_params(
271 std::size_t expected_rows, std::size_t actual_rows)
272 {
273 if (actual_rows != expected_rows)
274 {
275 throw unexpected_rows{
276 "Expected " + to_string(expected_rows) +
277 " row(s) of data "
278 "from parameterised query, got " +
279 to_string(actual_rows) + "."};
280 }
281 }
282
283
internal_exec_prepared(zview statement,internal::params const & args)284 pqxx::result pqxx::transaction_base::internal_exec_prepared(
285 zview statement, internal::params const &args)
286 {
287 return pqxx::internal::gate::connection_transaction{conn()}.exec_prepared(
288 statement, args);
289 }
290
291
internal_exec_params(std::string const & query,internal::params const & args)292 pqxx::result pqxx::transaction_base::internal_exec_params(
293 std::string const &query, internal::params const &args)
294 {
295 return pqxx::internal::gate::connection_transaction{conn()}.exec_params(
296 query, args);
297 }
298
299
set_variable(std::string_view var,std::string_view value)300 void pqxx::transaction_base::set_variable(
301 std::string_view var, std::string_view value)
302 {
303 conn().set_variable(var, value);
304 }
305
306
get_variable(std::string_view var)307 std::string pqxx::transaction_base::get_variable(std::string_view var)
308 {
309 return conn().get_variable(var);
310 }
311
312
close()313 void pqxx::transaction_base::close() noexcept
314 {
315 try
316 {
317 try
318 {
319 check_pending_error();
320 }
321 catch (std::exception const &e)
322 {
323 m_conn.process_notice(e.what());
324 }
325
326 if (m_registered)
327 {
328 m_registered = false;
329 pqxx::internal::gate::connection_transaction{conn()}
330 .unregister_transaction(this);
331 }
332
333 if (m_status != status::active)
334 return;
335
336 if (m_focus.get() != nullptr)
337 m_conn.process_notice(
338 "Closing " + description() + " with " + m_focus.get()->description() +
339 " still open.\n");
340
341 try
342 {
343 abort();
344 }
345 catch (std::exception const &e)
346 {
347 m_conn.process_notice(e.what());
348 }
349 }
350 catch (std::exception const &e)
351 {
352 try
353 {
354 m_conn.process_notice(e.what());
355 }
356 catch (std::exception const &)
357 {}
358 }
359 }
360
361
register_focus(internal::transactionfocus * s)362 void pqxx::transaction_base::register_focus(internal::transactionfocus *s)
363 {
364 m_focus.register_guest(s);
365 }
366
367
unregister_focus(internal::transactionfocus * s)368 void pqxx::transaction_base::unregister_focus(
369 internal::transactionfocus *s) noexcept
370 {
371 try
372 {
373 m_focus.unregister_guest(s);
374 }
375 catch (std::exception const &e)
376 {
377 m_conn.process_notice(std::string{e.what()} + "\n");
378 }
379 }
380
381
direct_exec(std::string_view c)382 pqxx::result pqxx::transaction_base::direct_exec(std::string_view c)
383 {
384 check_pending_error();
385 return pqxx::internal::gate::connection_transaction{conn()}.exec(c);
386 }
387
388
389 pqxx::result
direct_exec(std::shared_ptr<std::string> c)390 pqxx::transaction_base::direct_exec(std::shared_ptr<std::string> c)
391 {
392 check_pending_error();
393 return pqxx::internal::gate::connection_transaction{conn()}.exec(c);
394 }
395
396
register_pending_error(std::string const & err)397 void pqxx::transaction_base::register_pending_error(
398 std::string const &err) noexcept
399 {
400 if (std::empty(m_pending_error) and not std::empty(err))
401 {
402 try
403 {
404 m_pending_error = err;
405 }
406 catch (std::exception const &e)
407 {
408 try
409 {
410 process_notice("UNABLE TO PROCESS ERROR\n");
411 process_notice(e.what());
412 process_notice("ERROR WAS:");
413 process_notice(err);
414 }
415 catch (...)
416 {}
417 }
418 }
419 }
420
421
check_pending_error()422 void pqxx::transaction_base::check_pending_error()
423 {
424 if (not std::empty(m_pending_error))
425 {
426 std::string err;
427 err.swap(m_pending_error);
428 throw failure{err};
429 }
430 }
431
432
register_me()433 void pqxx::internal::transactionfocus::register_me()
434 {
435 pqxx::internal::gate::transaction_transactionfocus{m_trans}.register_focus(
436 this);
437 m_registered = true;
438 }
439
440
unregister_me()441 void pqxx::internal::transactionfocus::unregister_me() noexcept
442 {
443 pqxx::internal::gate::transaction_transactionfocus{m_trans}.unregister_focus(
444 this);
445 m_registered = false;
446 }
447
reg_pending_error(std::string const & err)448 void pqxx::internal::transactionfocus::reg_pending_error(
449 std::string const &err) noexcept
450 {
451 pqxx::internal::gate::transaction_transactionfocus{m_trans}
452 .register_pending_error(err);
453 }
454