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