GnuCash  5.6-150-g038405b370+
gnc-dbisqlconnection.cpp
1 /********************************************************************
2  * gnc-dbisqlconnection.cpp: Encapsulate libdbi dbi_conn *
3  * *
4  * Copyright 2016 John Ralls <jralls@ceridwen.us> *
5  * *
6  * This program is free software; you can redistribute it and/or *
7  * modify it under the terms of the GNU General Public License as *
8  * published by the Free Software Foundation; either version 2 of *
9  * the License, or (at your option) any later version. *
10  * *
11  * This program is distributed in the hope that it will be useful, *
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14  * GNU General Public License for more details. *
15  * *
16  * You should have received a copy of the GNU General Public License*
17  * along with this program; if not, contact: *
18  * *
19  * Free Software Foundation Voice: +1-617-542-5942 *
20  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21  * Boston, MA 02110-1301, USA gnu@gnu.org *
22 \********************************************************************/
23 
24 #include <guid.hpp>
25 #include <config.h>
26 #include <platform.h>
27 #include <gnc-locale-utils.h>
28 
29 #include <string>
30 #include <regex>
31 #include <sstream>
32 
33 #include "gnc-dbisqlconnection.hpp"
34 
35 static QofLogModule log_module = G_LOG_DOMAIN;
36 // gnc-dbiproviderimpl.hpp has templates that need log_module defined.
37 #include "gnc-dbiproviderimpl.hpp"
38 
39 static const unsigned int DBI_MAX_CONN_ATTEMPTS = 5;
40 const std::string lock_table = "gnclock";
41 
42 /* --------------------------------------------------------- */
44 {
45 public:
46  GncDbiSqlStatement(const std::string& sql) :
47  m_sql {sql} {}
48  ~GncDbiSqlStatement() {}
49  const char* to_sql() const override;
50  void add_where_cond(QofIdTypeConst, const PairVec&) override;
51 
52 private:
53  std::string m_sql;
54 };
55 
56 
57 const char*
58 GncDbiSqlStatement::to_sql() const
59 {
60  return m_sql.c_str();
61 }
62 
63 void
64 GncDbiSqlStatement::add_where_cond(QofIdTypeConst type_name,
65  const PairVec& col_values)
66 {
67  m_sql += " WHERE ";
68  for (auto colpair : col_values)
69  {
70  if (colpair != *col_values.begin())
71  m_sql += " AND ";
72  if (colpair.second == "NULL")
73  m_sql += colpair.first + " IS " + colpair.second;
74  else
75  m_sql += colpair.first + " = " + colpair.second;
76  }
77 }
78 
79 GncDbiSqlConnection::GncDbiSqlConnection (DbType type, QofBackend* qbe,
80  dbi_conn conn, SessionOpenMode mode) :
81  m_qbe{qbe}, m_conn{conn},
82  m_provider{type == DbType::DBI_SQLITE ?
83  make_dbi_provider<DbType::DBI_SQLITE>() :
84  type == DbType::DBI_MYSQL ?
85  make_dbi_provider<DbType::DBI_MYSQL>() :
86  make_dbi_provider<DbType::DBI_PGSQL>()},
87  m_conn_ok{true}, m_last_error{ERR_BACKEND_NO_ERR}, m_error_repeat{0},
88  m_retry{false}, m_sql_savepoint{0}, m_readonly{false}
89 {
90  if (mode == SESSION_READ_ONLY)
91  m_readonly = true;
92  else if (!lock_database(mode == SESSION_BREAK_LOCK))
93  throw std::runtime_error("Failed to lock database!");
94  if (!check_and_rollback_failed_save())
95  {
96  unlock_database();
97  throw std::runtime_error("A failed safe-save was detected and rolling it back failed.");
98  }
99 }
100 
101 bool
102 GncDbiSqlConnection::lock_database (bool break_lock)
103 {
104  const char *errstr;
105  /* Protect everything with a single transaction to prevent races */
106  if (!begin_transaction())
107  return false;
108  auto tables = m_provider->get_table_list(m_conn, lock_table);
109  if (tables.empty())
110  {
111  auto result = dbi_conn_queryf (m_conn,
112  "CREATE TABLE %s ( Hostname varchar(%d), PID int )",
113  lock_table.c_str(),
114  GNC_HOST_NAME_MAX);
115  if (result)
116  {
117  dbi_result_free (result);
118  result = nullptr;
119  }
120  if (dbi_conn_error (m_conn, &errstr))
121  {
122  PERR ("Error %s creating lock table", errstr);
124  return false;
125  }
126  }
127 
128  /* Check for an existing entry; delete it if break_lock is true, otherwise fail */
129  char hostname[ GNC_HOST_NAME_MAX + 1 ];
130  auto result = dbi_conn_queryf (m_conn, "SELECT * FROM %s",
131  lock_table.c_str());
132  if (result && dbi_result_get_numrows (result))
133  {
134  dbi_result_free (result);
135  result = nullptr;
136  if (!break_lock)
137  {
139  /* FIXME: After enhancing the qof_backend_error mechanism, report in the dialog what is the hostname of the machine holding the lock. */
141  return false;
142  }
143  result = dbi_conn_queryf (m_conn, "DELETE FROM %s", lock_table.c_str());
144  if (!result)
145  {
147  m_qbe->set_message("Failed to delete lock record");
149  return false;
150  }
151  dbi_result_free (result);
152  result = nullptr;
153  }
154  /* Add an entry and commit the transaction */
155  memset (hostname, 0, sizeof (hostname));
156  gethostname (hostname, GNC_HOST_NAME_MAX);
157  result = dbi_conn_queryf (m_conn,
158  "INSERT INTO %s VALUES ('%s', '%d')",
159  lock_table.c_str(), hostname, (int)GETPID ());
160  if (!result)
161  {
163  m_qbe->set_message("Failed to create lock record");
165  return false;
166  }
167  dbi_result_free (result);
168  return commit_transaction();
169 }
170 
171 void
172 GncDbiSqlConnection::unlock_database ()
173 {
174  if (m_conn == nullptr) return;
175  if (m_readonly) return;
176  auto dbi_error{dbi_conn_error (m_conn, nullptr)};
177  g_return_if_fail (dbi_error == DBI_ERROR_NONE || dbi_error == DBI_ERROR_BADIDX);
178 
179  auto tables = m_provider->get_table_list (m_conn, lock_table);
180  if (tables.empty())
181  {
182  PWARN ("No lock table in database, so not unlocking it.");
183  return;
184  }
185  if (begin_transaction())
186  {
187  /* Delete the entry if it's our hostname and PID */
188  char hostname[ GNC_HOST_NAME_MAX + 1 ];
189 
190  memset (hostname, 0, sizeof (hostname));
191  gethostname (hostname, GNC_HOST_NAME_MAX);
192  auto result = dbi_conn_queryf (m_conn,
193  "SELECT * FROM %s WHERE Hostname = '%s' "
194  "AND PID = '%d'", lock_table.c_str(),
195  hostname,
196  (int)GETPID ());
197  if (result && dbi_result_get_numrows (result))
198  {
199  if (result)
200  {
201  dbi_result_free (result);
202  result = nullptr;
203  }
204  result = dbi_conn_queryf (m_conn, "DELETE FROM %s",
205  lock_table.c_str());
206  if (!result)
207  {
208  PERR ("Failed to delete the lock entry");
211  return;
212  }
213  else
214  {
215  dbi_result_free (result);
216  result = nullptr;
217  }
219  return;
220  }
222  PWARN ("There was no lock entry in the Lock table");
223  return;
224  }
225  PWARN ("Unable to get a lock on LOCK, so failed to clear the lock entry.");
227 }
228 
229 bool
230 GncDbiSqlConnection::check_and_rollback_failed_save()
231 {
232  auto backup_tables = m_provider->get_table_list(m_conn, "%back");
233  if (backup_tables.empty())
234  return true;
235  auto merge_tables = m_provider->get_table_list(m_conn, "%_merge");
236  if (!merge_tables.empty())
237  {
238  PERR("Merge tables exist in the database indicating a previous"
239  "attempt to recover from a failed safe-save. Automatic"
240  "recovery is beyond GnuCash's ability, you must recover"
241  "by hand or restore from a good backup.");
242  return false;
243  }
244  return table_operation(recover);
245 }
246 
247 GncDbiSqlConnection::~GncDbiSqlConnection()
248 {
249  if (m_conn)
250  {
251  unlock_database();
252  dbi_conn_close(m_conn);
253  m_conn = nullptr;
254  }
255 }
256 
258 GncDbiSqlConnection::execute_select_statement (const GncSqlStatementPtr& stmt)
259  noexcept
260 {
261  dbi_result result;
262 
263  DEBUG ("SQL: %s\n", stmt->to_sql());
264  auto locale = gnc_push_locale (LC_NUMERIC, "C");
265  do
266  {
267  init_error ();
268  result = dbi_conn_query (m_conn, stmt->to_sql());
269  }
270  while (m_retry);
271  if (result == nullptr)
272  {
273  PERR ("Error executing SQL %s\n", stmt->to_sql());
274  if(m_last_error)
275  m_qbe->set_error(m_last_error);
276  else
277  m_qbe->set_error(ERR_BACKEND_SERVER_ERR);
278  }
279  gnc_pop_locale (LC_NUMERIC, locale);
280  return GncSqlResultPtr(new GncDbiSqlResult (this, result));
281 }
282 
283 int
284 GncDbiSqlConnection::execute_nonselect_statement (const GncSqlStatementPtr& stmt)
285  noexcept
286 {
287  dbi_result result;
288 
289  DEBUG ("SQL: %s\n", stmt->to_sql());
290  do
291  {
292  init_error ();
293  result = dbi_conn_query (m_conn, stmt->to_sql());
294  }
295  while (m_retry);
296  if (result == nullptr && m_last_error)
297  {
298  PERR ("Error executing SQL %s\n", stmt->to_sql());
299  if(m_last_error)
300  m_qbe->set_error(m_last_error);
301  else
302  m_qbe->set_error(ERR_BACKEND_SERVER_ERR);
303  return -1;
304  }
305  if (!result)
306  return 0;
307  auto num_rows = (gint)dbi_result_get_numrows_affected (result);
308  auto status = dbi_result_free (result);
309  if (status < 0)
310  {
311  PERR ("Error in dbi_result_free() result\n");
312  if(m_last_error)
313  m_qbe->set_error(m_last_error);
314  else
315  m_qbe->set_error(ERR_BACKEND_SERVER_ERR);
316  }
317  return num_rows;
318 }
319 
320 GncSqlStatementPtr
321 GncDbiSqlConnection::create_statement_from_sql (const std::string& sql)
322  const noexcept
323 {
324  return std::unique_ptr<GncSqlStatement>{new GncDbiSqlStatement (sql)};
325 }
326 
327 bool
328 GncDbiSqlConnection::does_table_exist (const std::string& table_name)
329  const noexcept
330 {
331  return ! m_provider->get_table_list(m_conn, table_name).empty();
332 }
333 
334 bool
336 {
337  dbi_result result;
338 
339  DEBUG ("BEGIN\n");
340 
341  if (!verify ())
342  {
343  PERR ("gnc_dbi_verify_conn() failed\n");
345  return false;
346  }
347 
348  do
349  {
350  init_error ();
351  if (m_sql_savepoint == 0)
352  result = dbi_conn_queryf (m_conn, "BEGIN");
353  else
354  {
355  std::ostringstream savepoint;
356  savepoint << "savepoint_" << m_sql_savepoint;
357  result = dbi_conn_queryf(m_conn, "SAVEPOINT %s",
358  savepoint.str().c_str());
359  }
360  }
361  while (m_retry);
362 
363  if (!result)
364  {
365  PERR ("BEGIN transaction failed()\n");
367  return false;
368  }
369  if (dbi_result_free (result) < 0)
370  {
371  PERR ("Error in dbi_result_free() result\n");
373  return false;
374  }
375  ++m_sql_savepoint;
376  return true;
377 }
378 
379 bool
381 {
382  DEBUG ("ROLLBACK\n");
383  if (m_sql_savepoint == 0) return false;
384  dbi_result result;
385  if (m_sql_savepoint == 1)
386  result = dbi_conn_query (m_conn, "ROLLBACK");
387  else
388  {
389  std::ostringstream savepoint;
390  savepoint << "savepoint_" << m_sql_savepoint - 1;
391  result = dbi_conn_queryf(m_conn, "ROLLBACK TO SAVEPOINT %s",
392  savepoint.str().c_str());
393  }
394  if (!result)
395  {
396  PERR ("Error in conn_rollback_transaction()\n");
398  return false;
399  }
400 
401  if (dbi_result_free (result) < 0)
402  {
403  PERR ("Error in dbi_result_free() result\n");
405  return false;
406  }
407 
408  --m_sql_savepoint;
409  return true;
410 }
411 
412 bool
414 {
415  DEBUG ("COMMIT\n");
416  if (m_sql_savepoint == 0) return false;
417  dbi_result result;
418  if (m_sql_savepoint == 1)
419  result = dbi_conn_queryf (m_conn, "COMMIT");
420  else
421  {
422  std::ostringstream savepoint;
423  savepoint << "savepoint_" << m_sql_savepoint - 1;
424  result = dbi_conn_queryf(m_conn, "RELEASE SAVEPOINT %s",
425  savepoint.str().c_str());
426  }
427 
428  if (!result)
429  {
430  PERR ("Error in conn_commit_transaction()\n");
432  return false;
433  }
434 
435  if (dbi_result_free (result) < 0)
436  {
437  PERR ("Error in dbi_result_free() result\n");
439  return false;
440  }
441  --m_sql_savepoint;
442  return true;
443 }
444 
445 
446 bool
447 GncDbiSqlConnection::create_table (const std::string& table_name,
448  const ColVec& info_vec) const noexcept
449 {
450  std::string ddl;
451  unsigned int col_num = 0;
452 
453  ddl += "CREATE TABLE " + table_name + "(";
454  for (auto const& info : info_vec)
455  {
456  if (col_num++ != 0)
457  {
458  ddl += ", ";
459  }
460  m_provider->append_col_def (ddl, info);
461  }
462  ddl += ")";
463 
464  if (ddl.empty())
465  return false;
466 
467  DEBUG ("SQL: %s\n", ddl.c_str());
468  auto result = dbi_conn_query (m_conn, ddl.c_str());
469  auto status = dbi_result_free (result);
470  if (status < 0)
471  {
472  PERR ("Error in dbi_result_free() result\n");
474  }
475 
476  return true;
477 }
478 
479 static std::string
480 create_index_ddl (const GncSqlConnection* conn, const std::string& index_name,
481  const std::string& table_name, const EntryVec& col_table)
482 {
483  std::string ddl;
484  ddl += "CREATE INDEX " + index_name + " ON " + table_name + "(";
485  for (const auto& table_row : col_table)
486  {
487  if (table_row != *col_table.begin())
488  {
489  ddl =+ ", ";
490  }
491  ddl += table_row->name();
492  }
493  ddl += ")";
494  return ddl;
495 }
496 
497 bool
498 GncDbiSqlConnection::create_index(const std::string& index_name,
499  const std::string& table_name,
500  const EntryVec& col_table) const noexcept
501 {
502  auto ddl = create_index_ddl (this, index_name, table_name, col_table);
503  if (ddl.empty())
504  return false;
505  DEBUG ("SQL: %s\n", ddl.c_str());
506  auto result = dbi_conn_query (m_conn, ddl.c_str());
507  auto status = dbi_result_free (result);
508  if (status < 0)
509  {
510  PERR ("Error in dbi_result_free() result\n");
512  }
513 
514  return true;
515 }
516 
517 bool
518 GncDbiSqlConnection::add_columns_to_table(const std::string& table_name,
519  const ColVec& info_vec)
520  const noexcept
521 {
522  auto ddl = add_columns_ddl(table_name, info_vec);
523  if (ddl.empty())
524  return false;
525 
526  DEBUG ("SQL: %s\n", ddl.c_str());
527  auto result = dbi_conn_query (m_conn, ddl.c_str());
528  auto status = dbi_result_free (result);
529  if (status < 0)
530  {
531  PERR( "Error in dbi_result_free() result\n" );
533  }
534 
535  return true;
536 }
537 
538 std::string
539 GncDbiSqlConnection::quote_string (const std::string& unquoted_str)
540  const noexcept
541 {
542  char* quoted_str;
543 
544  dbi_conn_quote_string_copy (m_conn, unquoted_str.c_str(),
545  &quoted_str);
546  if (quoted_str == nullptr)
547  return std::string{""};
548  std::string retval{quoted_str};
549  free(quoted_str);
550  return retval;
551 }
552 
553 
557 bool
559 {
560  if (m_conn_ok)
561  return true;
562 
563  /* We attempt to connect only once here. The error function will
564  * automatically re-attempt up until DBI_MAX_CONN_ATTEMPTS time to connect
565  * if this call fails. After all these attempts, conn_ok will indicate if
566  * there is a valid connection or not.
567  */
568  init_error ();
569  m_conn_ok = true;
570  (void)dbi_conn_connect (m_conn);
571 
572  return m_conn_ok;
573 }
574 
575 bool
576 GncDbiSqlConnection::retry_connection(const char* msg)
577  noexcept
578 {
579  while (m_retry && m_error_repeat <= DBI_MAX_CONN_ATTEMPTS)
580  {
581  m_conn_ok = false;
582  if (dbi_conn_connect(m_conn) == 0)
583  {
584  init_error();
585  m_conn_ok = true;
586  return true;
587  }
588 #ifdef G_OS_WIN32
589  const guint backoff_msecs = 1;
590  Sleep (backoff_msecs * 2 << ++m_error_repeat);
591 #else
592  const guint backoff_usecs = 1000;
593  usleep (backoff_usecs * 2 << ++m_error_repeat);
594 #endif
595  PINFO ("DBI error: %s - Reconnecting...\n", msg);
596 
597  }
598  PERR ("DBI error: %s - Giving up after %d consecutive attempts.\n", msg,
599  DBI_MAX_CONN_ATTEMPTS);
600  m_conn_ok = false;
601  return false;
602 }
603 
604 bool
605 GncDbiSqlConnection::rename_table(const std::string& old_name,
606  const std::string& new_name)
607 {
608  std::string sql = "ALTER TABLE " + old_name + " RENAME TO " + new_name;
609  auto stmt = create_statement_from_sql(sql);
610  return execute_nonselect_statement(stmt) >= 0;
611 }
612 
613 bool
614 GncDbiSqlConnection::drop_table(const std::string& table)
615 {
616  std::string sql = "DROP TABLE " + table;
617  auto stmt = create_statement_from_sql(sql);
618  return execute_nonselect_statement(stmt) >= 0;
619 }
620 
621 bool
622 GncDbiSqlConnection::merge_tables(const std::string& table,
623  const std::string& other)
624 {
625  auto merge_table = table + "_merge";
626  std::string sql = "CREATE TABLE " + merge_table + " AS SELECT * FROM " +
627  table + " UNION SELECT * FROM " + other;
628  auto stmt = create_statement_from_sql(sql);
629  if (execute_nonselect_statement(stmt) < 0)
630  return false;
631  if (!drop_table(table))
632  return false;
633  if (!rename_table(merge_table, table))
634  return false;
635  return drop_table(other);
636 }
637 
663 bool
664 GncDbiSqlConnection::table_operation(TableOpType op) noexcept
665 {
666  auto backup_tables = m_provider->get_table_list(m_conn, "%_back");
667  auto all_tables = m_provider->get_table_list(m_conn, "");
668  /* No operations on the lock table */
669  auto new_end = std::remove(all_tables.begin(), all_tables.end(), lock_table);
670  all_tables.erase(new_end, all_tables.end());
671  StrVec data_tables;
672  data_tables.reserve(all_tables.size() - backup_tables.size());
673  std::set_difference(all_tables.begin(), all_tables.end(),
674  backup_tables.begin(), backup_tables.end(),
675  std::back_inserter(data_tables));
676  switch(op)
677  {
678  case backup:
679  if (!backup_tables.empty())
680  {
681  PERR("Unable to backup database, an existing backup is present.");
683  return false;
684  }
685  for (auto table : data_tables)
686  if (!rename_table(table, table +"_back"))
687  return false; /* Error, trigger rollback. */
688  break;
689  case drop_backup:
690  for (auto table : backup_tables)
691  {
692  auto data_table = table.substr(0, table.find("_back"));
693  if (std::find(data_tables.begin(), data_tables.end(),
694  data_table) != data_tables.end())
695  drop_table(table); /* Other table exists, OK. */
696  else /* No data table, restore the backup */
697  rename_table(table, data_table);
698  }
699  break;
700  case rollback:
701  for (auto table : backup_tables)
702  {
703  auto data_table = table.substr(0, table.find("_back"));
704  if (std::find(data_tables.begin(), data_tables.end(),
705  data_table) != data_tables.end())
706  drop_table(data_table); /* Other table exists, OK. */
707  rename_table(table, data_table);
708  }
709  break;
710  case recover:
711  for (auto table : backup_tables)
712  {
713  auto data_table = table.substr(0, table.find("_back"));
714  if (std::find(data_tables.begin(), data_tables.end(),
715  data_table) != data_tables.end())
716  {
717  if (!merge_tables(data_table, table))
718  return false;
719  }
720  else
721  {
722  if (!rename_table(table, data_table))
723  return false;
724  }
725  }
726  break;
727  }
728  return true;
729 }
730 
731 bool
732 GncDbiSqlConnection::drop_indexes() noexcept
733 {
734  auto index_list = m_provider->get_index_list (m_conn);
735  for (auto index : index_list)
736  {
737  const char* errmsg;
738  m_provider->drop_index (m_conn, index);
739  if (DBI_ERROR_NONE != dbi_conn_error (m_conn, &errmsg))
740  {
741  PERR("Failed to drop indexes %s", errmsg);
742  return false;
743  }
744  }
745  return true;
746 }
747 
748 std::string
749 GncDbiSqlConnection::add_columns_ddl(const std::string& table_name,
750  const ColVec& info_vec) const noexcept
751 {
752  std::string ddl;
753 
754  ddl += "ALTER TABLE " + table_name;
755  for (auto const& info : info_vec)
756  {
757  if (info != *info_vec.begin())
758  {
759  ddl += ", ";
760  }
761  ddl += "ADD COLUMN ";
762  m_provider->append_col_def (ddl, info);
763  }
764  return ddl;
765 }
bool verify() noexcept override
Check if the dbi connection is valid.
void set_message(std::string &&)
Set a descriptive message that can be displayed to the user when there&#39;s an error.
Definition: qof-backend.cpp:79
#define G_LOG_DOMAIN
Functions providing the SX List as a plugin page.
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
bool does_table_exist(const std::string &) const noexcept override
Returns true if successful.
const gchar * QofIdTypeConst
QofIdTypeConst declaration.
Definition: qofid.h:82
void qof_backend_set_error(QofBackend *qof_be, QofBackendError err)
Set the error on the specified QofBackend.
SQL statement provider.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
in use by another user (ETXTBSY)
Definition: qofbackend.h:66
Open the session read-only, ignoring any existing lock and not creating one if the URI isn&#39;t locked...
Definition: qofsession.h:130
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
Create a new store at the URI even if a store already exists there.
Definition: qofsession.h:128
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
error in response from server
Definition: qofbackend.h:71
bool table_operation(TableOpType op) noexcept
Perform a specified SQL operation on every table in a database.
bool commit_transaction() noexcept override
Returns TRUE if successful, FALSE if error.
bool create_index(const std::string &, const std::string &, const EntryVec &) const noexcept override
Returns TRUE if successful, FALSE if error.
bool begin_transaction() noexcept override
Returns TRUE if successful, false if error.
data in db is corrupt
Definition: qofbackend.h:70
SessionOpenMode
Mode for opening sessions.
Definition: qofsession.h:120
bool add_columns_to_table(const std::string &, const ColVec &) const noexcept override
Returns TRUE if successful, FALSE if error.
Encapsulate the connection to the database.
An iterable wrapper for dbi_result; allows using C++11 range for.
Pure virtual class to iterate over a query result set.
bool create_table(const std::string &, const ColVec &) const noexcept override
Returns TRUE if successful, FALSE if error.
bool rollback_transaction() noexcept override
Returns TRUE if successful, FALSE if error.
int execute_nonselect_statement(const GncSqlStatementPtr &) noexcept override
Returns false if error.
void set_error(QofBackendError err)
Set the error value only if there isn&#39;t already an error already.
Definition: qof-backend.cpp:56