GnuCash  5.6-150-g038405b370+
gnc-backend-dbi.cpp
1 /********************************************************************
2  * gnc-backend-dbi.c: load and save data to SQL via libdbi *
3  * *
4  * This program is free software; you can redistribute it and/or *
5  * modify it under the terms of the GNU General Public License as *
6  * published by the Free Software Foundation; either version 2 of *
7  * the License, or (at your option) any later version. *
8  * *
9  * This program is distributed in the hope that it will be useful, *
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12  * GNU General Public License for more details. *
13  * *
14  * You should have received a copy of the GNU General Public License*
15  * along with this program; if not, contact: *
16  * *
17  * Free Software Foundation Voice: +1-617-542-5942 *
18  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
19  * Boston, MA 02110-1301, USA gnu@gnu.org *
20 \********************************************************************/
28 #include <glib.h>
29 #include <glib/gstdio.h>
30 
31 #include "config.h"
32 
33 #include <platform.h>
34 #if PLATFORM(WINDOWS)
35 #include <winsock2.h>
36 #include <windows.h>
37 #endif
38 
39 #include <inttypes.h>
40 #include <errno.h>
41 #include "qof.h"
42 #include "qofquery-p.h"
43 #include "qofquerycore-p.h"
44 #include "Account.h"
45 #include "TransLog.h"
46 #include "gnc-engine.h"
47 #include "SX-book.h"
48 #include "Recurrence.h"
49 #include <gnc-features.h>
50 #include "gnc-uri-utils.h"
51 #include "gnc-filepath-utils.h"
52 #include <gnc-path.h>
53 #include "gnc-locale-utils.h"
54 
55 #include "gnc-prefs.h"
56 
57 #ifdef S_SPLINT_S
58 #include "splint-defs.h"
59 #endif
60 
61 #include <boost/regex.hpp>
62 #include <string>
63 #include <iomanip>
64 
65 #include <qofsession.hpp>
66 #include <gnc-backend-prov.hpp>
67 #include "gnc-backend-dbi.h"
68 #include "gnc-backend-dbi.hpp"
69 
70 #include <gnc-sql-object-backend.hpp>
71 #include "gnc-dbisqlresult.hpp"
72 #include "gnc-dbisqlconnection.hpp"
73 
74 #if LIBDBI_VERSION >= 900
75 #define HAVE_LIBDBI_R 1
76 static dbi_inst dbi_instance = nullptr;
77 #else
78 #define HAVE_LIBDBI_R 0
79 #define HAVE_LIBDBI_TO_LONGLONG 0
80 #endif
81 
82 #define TRANSACTION_NAME "trans"
83 
84 static QofLogModule log_module = G_LOG_DOMAIN;
85 
86 #define FILE_URI_TYPE "file"
87 #define FILE_URI_PREFIX (FILE_URI_TYPE "://")
88 #define SQLITE3_URI_TYPE "sqlite3"
89 #define SQLITE3_URI_PREFIX (SQLITE3_URI_TYPE "://")
90 #define PGSQL_DEFAULT_PORT 5432
91 
92 static void adjust_sql_options (dbi_conn connection);
93 template<DbType Type> bool save_may_clobber_data (dbi_conn conn,
94  const std::string& dbname);
95 
96 template <DbType Type>
98 {
99 public:
100  QofDbiBackendProvider (const char* name, const char* type) :
101  QofBackendProvider {name, type} {}
103  QofDbiBackendProvider operator=(QofDbiBackendProvider&) = delete;
105  QofDbiBackendProvider operator=(QofDbiBackendProvider&&) = delete;
106  ~QofDbiBackendProvider () = default;
108  {
109  return new GncDbiBackend<Type>(nullptr, nullptr);
110  }
111  bool type_check(const char* type) { return true; }
112 };
113 
114 /* ================================================================= */
115 /* ================================================================= */
117 {
118  UriStrings() = default;
119  UriStrings(const std::string& uri);
120  ~UriStrings() = default;
121  std::string basename() const noexcept;
122  const char* dbname() const noexcept;
123  std::string quote_dbname(DbType t) const noexcept;
124  std::string m_protocol;
125  std::string m_host;
126  std::string m_dbname;
127  std::string m_username;
128  std::string m_password;
129  std::string m_basename;
130  int m_portnum;
131 };
132 
133 UriStrings::UriStrings(const std::string& uri)
134 {
135  gchar *scheme, *host, *username, *password, *dbname;
136  int portnum;
137  gnc_uri_get_components(uri.c_str(), &scheme, &host, &portnum, &username,
138  &password, &dbname);
139  m_protocol = std::string{scheme};
140  m_host = std::string{host};
141  if (dbname)
142  m_dbname = std::string{dbname};
143  if (username)
144  m_username = std::string{username};
145  if (password)
146  m_password = std::string{password};
147  m_portnum = portnum;
148  g_free(scheme);
149  g_free(host);
150  g_free(username);
151  g_free(password);
152  g_free(dbname);
153 }
154 
155 std::string
156 UriStrings::basename() const noexcept
157 {
158  return m_protocol + "_" + m_host + "_" + m_username + "_" + m_dbname;
159 }
160 
161 const char*
162 UriStrings::dbname() const noexcept
163 {
164  return m_dbname.c_str();
165 }
166 
167 std::string
168 UriStrings::quote_dbname(DbType t) const noexcept
169 {
170  if (m_dbname.empty())
171  return "";
172  const char quote = (t == DbType::DBI_MYSQL ? '`' : '"');
173  std::string retval(1, quote);
174  retval += m_dbname + quote;
175  return retval;
176 }
177 
178 static void
179 set_options(dbi_conn conn, const PairVec& options)
180 {
181  for (const auto& option : options)
182  {
183  auto opt = option.first.c_str();
184  auto val = option.second.c_str();
185  auto result = dbi_conn_set_option(conn, opt, val);
186  if (result < 0)
187  {
188  const char *msg = nullptr;
189  dbi_conn_error(conn, &msg);
190  PERR("Error setting %s option to %s: %s", opt, val, msg);
191  throw std::runtime_error(msg);
192  }
193  }
194 }
195 
203 template <DbType Type> bool
205  const UriStrings& uri)
206 
207 {
208  PairVec options;
209  options.push_back(std::make_pair("host", uri.m_host));
210  options.push_back(std::make_pair("dbname", uri.m_dbname));
211  options.push_back(std::make_pair("username", uri.m_username));
212  options.push_back(std::make_pair("password", uri.m_password));
213  options.push_back(std::make_pair("encoding", "UTF-8"));
214  try
215  {
216  set_options(conn, options);
217  auto result = dbi_conn_set_option_numeric(conn, "port", uri.m_portnum);
218  if (result < 0)
219  {
220  const char *msg = nullptr;
221  auto err = dbi_conn_error(conn, &msg);
222  PERR("Error (%d) setting port option to %d: %s", err, uri.m_portnum, msg);
223  throw std::runtime_error(msg);
224  }
225  }
226  catch (std::runtime_error& err)
227  {
228  set_error (ERR_BACKEND_SERVER_ERR);
229  return false;
230  }
231 
232  return true;
233 }
234 
235 template <DbType Type> void error_handler(dbi_conn conn, void* data);
236 void error_handler(dbi_conn conn, void* data);
237 
238 template <DbType Type> dbi_conn
239 GncDbiBackend<Type>::conn_setup (PairVec& options, UriStrings& uri)
240 {
241  const char* dbstr = (Type == DbType::DBI_SQLITE ? "sqlite3" :
242  Type == DbType::DBI_MYSQL ? "mysql" : "pgsql");
243 #if HAVE_LIBDBI_R
244  dbi_conn conn = nullptr;
245  if (dbi_instance)
246  conn = dbi_conn_new_r (dbstr, dbi_instance);
247  else
248  PERR ("Attempt to connect with an uninitialized dbi_instance");
249 #else
250  auto conn = dbi_conn_new (dbstr);
251 #endif
252 
253  if (conn == nullptr)
254  {
255  PERR ("Unable to create %s dbi connection", dbstr);
256  set_error (ERR_BACKEND_BAD_URL);
257  return nullptr;
258  }
259 
260  dbi_conn_error_handler (conn, error_handler<Type>, this);
261  if (!uri.m_dbname.empty() &&
262  !set_standard_connection_options(conn, uri))
263  {
264  dbi_conn_close(conn);
265  return nullptr;
266  }
267  if(!options.empty())
268  {
269  try {
270  set_options(conn, options);
271  }
272  catch (std::runtime_error& err)
273  {
274  dbi_conn_close(conn);
275  set_error (ERR_BACKEND_SERVER_ERR);
276  return nullptr;
277  }
278  }
279 
280  return conn;
281 }
282 
283 template <DbType Type>bool
284 GncDbiBackend<Type>::create_database(dbi_conn conn, const char* db)
285 {
286  const char *dbname;
287  const char *dbcreate;
288  if (Type == DbType::DBI_MYSQL)
289  {
290  dbname = "mysql";
291  dbcreate = "CREATE DATABASE %s CHARACTER SET utf8";
292  }
293  else
294  {
295  dbname = "postgres";
296  dbcreate = "CREATE DATABASE %s WITH TEMPLATE template0 ENCODING 'UTF8'";
297  }
298  PairVec options;
299  options.push_back(std::make_pair("dbname", dbname));
300  try
301  {
302  set_options(conn, options);
303  }
304  catch (std::runtime_error& err)
305  {
306  set_error (ERR_BACKEND_SERVER_ERR);
307  return false;
308  }
309 
310  auto result = dbi_conn_connect (conn);
311  if (result < 0)
312  {
313  PERR ("Unable to connect to %s database", dbname);
314  set_error(ERR_BACKEND_SERVER_ERR);
315  return false;
316  }
317  if (Type == DbType::DBI_MYSQL)
318  adjust_sql_options(conn);
319  auto dresult = dbi_conn_queryf (conn, dbcreate, db);
320  if (dresult == nullptr)
321  {
322  PERR ("Unable to create database '%s'\n", db);
323  set_error (ERR_BACKEND_SERVER_ERR);
324  return false;
325  }
326  if (Type == DbType::DBI_PGSQL)
327  {
328  const char *alterdb = "ALTER DATABASE %s SET "
329  "standard_conforming_strings TO on";
330  dbi_conn_queryf (conn, alterdb, db);
331  }
332  dbi_conn_close(conn);
333  conn = nullptr;
334  return true;
335 }
336 
337 template <> void
338 error_handler<DbType::DBI_SQLITE> (dbi_conn conn, void* user_data)
339 {
340  const char* msg;
342  static_cast<decltype(dbi_be)>(user_data);
343  int err_num = dbi_conn_error (conn, &msg);
344  /* BADIDX is raised if we attempt to seek outside of a result. We
345  * handle that possibility after checking the return value of the
346  * seek. Having this raise a critical error breaks looping by
347  * testing for the return value of the seek.
348  */
349  if (err_num == DBI_ERROR_BADIDX) return;
350  PERR ("DBI error: %s\n", msg);
351  if (dbi_be->connected())
352  dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, false);
353 }
354 
355 template <> void
357  const char* new_uri,
358  SessionOpenMode mode)
359 {
360  gboolean file_exists;
361  PairVec options;
362 
363  g_return_if_fail (session != nullptr);
364  g_return_if_fail (new_uri != nullptr);
365 
366  ENTER (" ");
367 
368  /* Remove uri type if present */
369  auto path = gnc_uri_get_path (new_uri);
370  std::string filepath{path};
371  g_free(path);
372  GFileTest ftest = static_cast<decltype (ftest)> (
373  G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS) ;
374  file_exists = g_file_test (filepath.c_str(), ftest);
375  bool create{mode == SESSION_NEW_STORE || mode == SESSION_NEW_OVERWRITE};
376  if (!create && !file_exists)
377  {
378  set_error (ERR_FILEIO_FILE_NOT_FOUND);
379  std::string msg{"Sqlite3 file "};
380  set_message (msg + filepath + " not found");
381  PWARN ("Sqlite3 file %s not found", filepath.c_str());
382  LEAVE("Error");
383  return;
384  }
385 
386  if (create && file_exists)
387  {
388  if (mode == SESSION_NEW_OVERWRITE)
389  g_unlink (filepath.c_str());
390  else
391  {
392  set_error (ERR_BACKEND_STORE_EXISTS);
393  auto msg = "Might clobber, mode not SESSION_NEW_OVERWRITE";
394  PWARN ("%s", msg);
395  LEAVE("Error");
396  return;
397  }
398  }
399 
400  connect(nullptr);
401  /* dbi-sqlite3 documentation says that sqlite3 doesn't take a "host" option */
402  options.push_back(std::make_pair("host", "localhost"));
403  auto dirname = g_path_get_dirname (filepath.c_str());
404  auto basename = g_path_get_basename (filepath.c_str());
405  options.push_back(std::make_pair("dbname", basename));
406  options.push_back(std::make_pair("sqlite3_dbdir", dirname));
407  if (basename != nullptr) g_free (basename);
408  if (dirname != nullptr) g_free (dirname);
409  UriStrings uri;
410  auto conn = conn_setup(options, uri);
411  if (conn == nullptr)
412  {
413  LEAVE("Error");
414  return;
415  }
416 
417  auto result = dbi_conn_connect (conn);
418 
419  if (result < 0)
420  {
421  dbi_conn_close(conn);
422  PERR ("Unable to connect to %s: %d\n", new_uri, result);
423  set_error (ERR_BACKEND_BAD_URL);
424  LEAVE("Error");
425  return;
426  }
427 
428  if (!conn_test_dbi_library(conn))
429  {
430  if (create && !file_exists)
431  {
432  /* File didn't exist before, but it does now, and we don't want to
433  * leave it lying around.
434  */
435  dbi_conn_close (conn);
436  conn = nullptr;
437  g_unlink (filepath.c_str());
438  }
439  dbi_conn_close(conn);
440  LEAVE("Bad DBI Library");
441  return;
442  }
443 
444  try
445  {
446  connect(new GncDbiSqlConnection(DbType::DBI_SQLITE,
447  this, conn, mode));
448  }
449  catch (std::runtime_error& err)
450  {
451  return;
452  }
453 
454  /* We should now have a proper session set up.
455  * Let's start logging */
456  xaccLogSetBaseName (filepath.c_str());
457  PINFO ("logpath=%s", filepath.c_str() ? filepath.c_str() : "(null)");
458  LEAVE ("");
459 }
460 
461 
462 template <> void
463 error_handler<DbType::DBI_MYSQL> (dbi_conn conn, void* user_data)
464 {
466  static_cast<decltype(dbi_be)>(user_data);
467  const char* msg;
468 
469  auto err_num = dbi_conn_error (conn, &msg);
470  /* BADIDX is raised if we attempt to seek outside of a result. We
471  * handle that possibility after checking the return value of the
472  * seek. Having this raise a critical error breaks looping by
473  * testing for the return value of the seek.
474  */
475  if (err_num == DBI_ERROR_BADIDX) return;
476 
477  /* Note: the sql connection may not have been initialized yet
478  * so let's be careful with using it
479  */
480 
481  /* Database doesn't exist. When this error is triggered the
482  * GncDbiSqlConnection may not exist yet either, so don't use it here
483  */
484  if (err_num == 1049) // Database doesn't exist
485  {
486  PINFO ("DBI error: %s\n", msg);
487  dbi_be->set_exists(false);
488  return;
489  }
490 
491  /* All the other error handling code assumes the GncDbiSqlConnection
492  * has been initialized. So let's assert it exits here, otherwise
493  * simply return.
494  */
495  if (!dbi_be->connected())
496  {
497  PINFO ("DBI error: %s\n", msg);
498  PINFO ("Note: GncDbiSqlConnection not yet initialized. Skipping further error processing.");
499  return;
500  }
501 
502  /* Test for other errors */
503  if (err_num == 2006) // Server has gone away
504  {
505  PINFO ("DBI error: %s - Reconnecting...\n", msg);
506  dbi_be->set_dbi_error (ERR_BACKEND_CONN_LOST, 1, true);
507  dbi_be->retry_connection(msg);
508  }
509  else if (err_num == 2003) // Unable to connect
510  {
511  dbi_be->set_dbi_error (ERR_BACKEND_CANT_CONNECT, 1, true);
512  dbi_be->retry_connection (msg);
513  }
514  else if (err_num == 1007) //Database exists
515  {
516  dbi_be->set_exists(true);
517  return;
518  }
519 
520  else // Any other error
521  {
522  PERR ("DBI error: %s\n", msg);
523  dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, FALSE);
524  }
525 }
526 
527 #define SQL_OPTION_TO_REMOVE "NO_ZERO_DATE"
528 
529 /* Given an sql_options string returns a copy of the string adjusted as
530  * necessary. In particular if string the contains SQL_OPTION_TO_REMOVE it is
531  * removed along with comma separator.
532  */
533 std::string
534 adjust_sql_options_string(const std::string& str)
535 {
536 /* Regex that finds the SQL_OPTION_TO_REMOVE as the first, last, or middle of a
537  * comma-delimited list.
538  */
539  boost::regex reg{"(?:," SQL_OPTION_TO_REMOVE "$|\\b"
540  SQL_OPTION_TO_REMOVE "\\b,?)"};
541  return regex_replace(str, reg, std::string{""});
542 }
543 
544 /* checks mysql sql_options and adjusts if necessary */
545 static void
546 adjust_sql_options (dbi_conn connection)
547 {
548  dbi_result result = dbi_conn_query( connection, "SELECT @@sql_mode");
549  if (result == nullptr)
550  {
551  const char* errmsg;
552  int err = dbi_conn_error(connection, &errmsg);
553  PERR("Unable to read sql_mode %d : %s", err, errmsg);
554  return;
555  }
556  dbi_result_first_row(result);
557  std::string str{dbi_result_get_string_idx(result, 1)};
558  dbi_result_free(result);
559  if (str.empty())
560  {
561  const char* errmsg;
562  int err = dbi_conn_error(connection, &errmsg);
563  if (err)
564  PERR("Unable to get sql_mode %d : %s", err, errmsg);
565  else
566  PINFO("Sql_mode isn't set.");
567  return;
568  }
569  PINFO("Initial sql_mode: %s", str.c_str());
570  if(str.find(SQL_OPTION_TO_REMOVE) != std::string::npos)
571  str = adjust_sql_options_string(str);
572 
573  //https://bugs.gnucash.org/show_bug.cgi?id=798112
574  const char* backslash_option{"NO_BACKSLASH_ESCAPES"};
575 
576  if (str.find(backslash_option) == std::string::npos)
577  {
578  if (!str.empty())
579  str.append(",");
580  str.append(backslash_option);
581  }
582 
583  PINFO("Setting sql_mode to %s", str.c_str());
584  std::string set_str{"SET sql_mode='" + std::move(str) + "'"};
585  dbi_result set_result = dbi_conn_query(connection,
586  set_str.c_str());
587  if (set_result)
588  {
589  dbi_result_free(set_result);
590  }
591  else
592  {
593  const char* errmsg;
594  int err = dbi_conn_error(connection, &errmsg);
595  PERR("Unable to set sql_mode %d : %s", err, errmsg);
596  }
597 }
598 
599 template <DbType Type> bool
600 drop_database(dbi_conn conn, const UriStrings& uri)
601 {
602  const char *root_db;
603  if (Type == DbType::DBI_PGSQL)
604  {
605  root_db = "template1";
606  }
607  else if (Type == DbType::DBI_MYSQL)
608  {
609  root_db = "mysql";
610  }
611  else
612  {
613  PERR ("Unknown database type, can't proceed.");
614  LEAVE("Error");
615  return false;
616  }
617  if (dbi_conn_select_db (conn, root_db) == -1)
618  {
619  PERR ("Failed to switch out of %s, drop will fail.",
620  uri.quote_dbname(Type).c_str());
621  LEAVE ("Error");
622  return false;
623  }
624  if (!dbi_conn_queryf (conn, "DROP DATABASE %s",
625  uri.quote_dbname(Type).c_str()))
626  {
627  PERR ("Failed to drop database %s prior to recreating it."
628  "Proceeding would combine old and new data.",
629  uri.quote_dbname(Type).c_str());
630  LEAVE ("Error");
631  return false;
632  }
633  return true;
634 }
635 
636 template <DbType Type> void
637 GncDbiBackend<Type>::session_begin (QofSession* session, const char* new_uri,
638  SessionOpenMode mode)
639 {
640  PairVec options;
641 
642  g_return_if_fail (session != nullptr);
643  g_return_if_fail (new_uri != nullptr);
644 
645  ENTER (" ");
646 
647  /* Split the book-id
648  * Format is protocol://username:password@hostname:port/dbname
649  where username, password and port are optional) */
650  UriStrings uri(new_uri);
651 
652  if (Type == DbType::DBI_PGSQL)
653  {
654  if (uri.m_portnum == 0)
655  uri.m_portnum = PGSQL_DEFAULT_PORT;
656  /* Postgres's SQL interface coerces identifiers to lower case, but the
657  * C interface is case-sensitive. This results in a mixed-case dbname
658  * being created (with a lower case name) but then dbi can't connect to
659  * it. To work around this, coerce the name to lowercase first. */
660  auto lcname = g_utf8_strdown (uri.dbname(), -1);
661  uri.m_dbname = std::string{lcname};
662  g_free(lcname);
663  }
664  connect(nullptr);
665 
666  bool create{mode == SESSION_NEW_STORE || mode == SESSION_NEW_OVERWRITE};
667  auto conn = conn_setup(options, uri);
668  if (conn == nullptr)
669  {
670  LEAVE("Error");
671  return;
672  }
673 
674  m_exists = true; //May be unset in the error handler.
675  auto result = dbi_conn_connect (conn);
676  if (result == 0)
677  {
678  if (Type == DbType::DBI_MYSQL)
679  adjust_sql_options (conn);
680  if(!conn_test_dbi_library(conn))
681  {
682  dbi_conn_close(conn);
683  LEAVE("Error");
684  return;
685  }
686  bool create = (mode == SESSION_NEW_STORE ||
687  mode == SESSION_NEW_OVERWRITE);
688  if (create && save_may_clobber_data<Type>(conn, uri.quote_dbname(Type)))
689  {
690  if (mode == SESSION_NEW_OVERWRITE)
691  {
692  if (!drop_database<Type>(conn, uri))
693  return;
694  }
695  else
696  {
697  set_error (ERR_BACKEND_STORE_EXISTS);
698  PWARN ("Database already exists, Might clobber it.");
699  dbi_conn_close(conn);
700  LEAVE("Error");
701  return;
702  }
703  /* Drop successful. */
704  m_exists = false;
705  }
706 
707  }
708  else if (m_exists)
709  {
710  PERR ("Unable to connect to database '%s'\n", uri.dbname());
711  set_error (ERR_BACKEND_CANT_CONNECT);
712  dbi_conn_close(conn);
713  LEAVE("Error");
714  return;
715  }
716  else if (!create)
717  {
718  PERR ("Database '%s' does not exist\n", uri.dbname());
719  set_error(ERR_BACKEND_NO_SUCH_DB);
720  std::string msg{"Database "};
721  set_message(msg + uri.dbname() + " not found");
722  LEAVE("Error");
723  return;
724  }
725 
726  if (create)
727  {
728  if (!m_exists &&
729  !create_database(conn, uri.quote_dbname(Type).c_str()))
730  {
731  dbi_conn_close(conn);
732  LEAVE("Error");
733  return;
734  }
735  conn = conn_setup(options, uri);
736  result = dbi_conn_connect (conn);
737  if (result < 0)
738  {
739  PERR ("Unable to create database '%s'\n", uri.dbname());
740  set_error (ERR_BACKEND_SERVER_ERR);
741  dbi_conn_close(conn);
742  LEAVE("Error");
743  return;
744  }
745  if (Type == DbType::DBI_MYSQL)
746  adjust_sql_options (conn);
747  if (!conn_test_dbi_library(conn))
748  {
749  if (Type == DbType::DBI_PGSQL)
750  dbi_conn_select_db (conn, "template1");
751  dbi_conn_queryf (conn, "DROP DATABASE %s",
752  uri.quote_dbname(Type).c_str());
753  dbi_conn_close(conn);
754  return;
755  }
756  }
757 
758  connect(nullptr);
759  try
760  {
761  connect(new GncDbiSqlConnection(Type, this, conn, mode));
762  }
763  catch (std::runtime_error& err)
764  {
765  return;
766  }
767  /* We should now have a proper session set up.
768  * Let's start logging */
769  auto translog_path = gnc_build_translog_path (uri.basename().c_str());
770  xaccLogSetBaseName (translog_path);
771  PINFO ("logpath=%s", translog_path ? translog_path : "(null)");
772  g_free (translog_path);
773 
774  LEAVE (" ");
775 }
776 
777 template<> void
778 error_handler<DbType::DBI_PGSQL> (dbi_conn conn, void* user_data)
779 {
781  static_cast<decltype(dbi_be)>(user_data);
782  const char* msg;
783 
784  auto err_num = dbi_conn_error (conn, &msg);
785  /* BADIDX is raised if we attempt to seek outside of a result. We
786  * handle that possibility after checking the return value of the
787  * seek. Having this raise a critical error breaks looping by
788  * testing for the return value of the seek.
789  */
790  if (err_num == DBI_ERROR_BADIDX) return;
791  if (g_str_has_prefix (msg, "FATAL: database") &&
792  g_str_has_suffix (msg, "does not exist\n"))
793  {
794  PINFO ("DBI error: %s\n", msg);
795  dbi_be->set_exists(false);
796  }
797  else if (g_strrstr (msg,
798  "server closed the connection unexpectedly")) // Connection lost
799  {
800  if (!dbi_be->connected())
801  {
802  PWARN ("DBI Error: Connection lost, connection pointer invalid");
803  return;
804  }
805  PINFO ("DBI error: %s - Reconnecting...\n", msg);
806  dbi_be->set_dbi_error (ERR_BACKEND_CONN_LOST, 1, true);
807  dbi_be->retry_connection(msg);
808  }
809  else if (g_str_has_prefix (msg, "connection pointer is NULL") ||
810  g_str_has_prefix (msg, "could not connect to server")) // No connection
811  {
812 
813  if (!dbi_be->connected())
814  qof_backend_set_error(reinterpret_cast<QofBackend*>(dbi_be),
816  else
817  {
818  dbi_be->set_dbi_error(ERR_BACKEND_CANT_CONNECT, 1, true);
819  dbi_be->retry_connection (msg);
820  }
821  }
822  else
823  {
824  PERR ("DBI error: %s\n", msg);
825  if (dbi_be->connected())
826  dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, false);
827  }
828 }
829 
830 /* ================================================================= */
831 
832 template <DbType Type> void
834 {
835  ENTER (" ");
836 
837  finalize_version_info ();
838  connect(nullptr);
839 
840  LEAVE (" ");
841 }
842 
843 template <DbType Type>
845 {
846  /* Stop transaction logging */
847  xaccLogSetBaseName (nullptr);
848 }
849 
850 /* ================================================================= */
851 
852 /* GNUCASH_RESAVE_VERSION indicates the earliest database version
853  * compatible with this version of Gnucash; the stored value is the
854  * earliest version of Gnucash conpatible with the database. If the
855  * GNUCASH_RESAVE_VERSION for this Gnucash is newer than the Gnucash
856  * version which created the database, a resave is offered. If the
857  * version of this Gnucash is older than the saved resave version,
858  * then the database will be loaded read-only. A resave will update
859  * both values to match this version of Gnucash.
860  */
861 template <DbType Type> void
862 GncDbiBackend<Type>::load (QofBook* book, QofBackendLoadType loadType)
863 {
864  g_return_if_fail (book != nullptr);
865 
866  ENTER ("dbi_be=%p, book=%p", this, book);
867 
868  if (loadType == LOAD_TYPE_INITIAL_LOAD)
869  {
870 
871  // Set up table version information
872  init_version_info ();
873  assert (m_book == nullptr);
874  create_tables();
875  }
876 
877  GncSqlBackend::load(book, loadType);
878 
879  if (Type == DbType::DBI_SQLITE)
880  gnc_features_set_used(book, GNC_FEATURE_SQLITE3_ISO_DATES);
881 
882  if (GNUCASH_RESAVE_VERSION > get_table_version("Gnucash"))
883  {
884  /* The database was loaded with an older database schema or
885  * data semantics. In order to ensure consistency, the whole
886  * thing needs to be saved anew. */
887  set_error(ERR_SQL_DB_TOO_OLD);
888  }
889  else if (GNUCASH_RESAVE_VERSION < get_table_version("Gnucash-Resave"))
890  {
891  /* Worse, the database was created with a newer version. We
892  * can't safely write to this database, so the user will have
893  * to do a "save as" to make one that we can write to.
894  */
895  set_error(ERR_SQL_DB_TOO_NEW);
896  }
897 
898 
899  LEAVE ("");
900 }
901 
902 /* ================================================================= */
903 /* This is used too early to call GncDbiProvider::get_table_list(). */
904 template <DbType T> bool
905 save_may_clobber_data (dbi_conn conn, const std::string& dbname)
906 {
907 
908  /* Data may be clobbered iff the number of tables != 0 */
909  auto result = dbi_conn_get_table_list (conn, dbname.c_str(), nullptr);
910  bool retval = false;
911  if (result)
912  {
913  retval = dbi_result_get_numrows (result) > 0;
914  dbi_result_free (result);
915  }
916  return retval;
917 }
918 
919 template <> bool
920 save_may_clobber_data <DbType::DBI_PGSQL>(dbi_conn conn,
921  const std::string& dbname)
922 {
923 
924  /* Data may be clobbered iff the number of tables != 0 */
925  const char* query = "SELECT relname FROM pg_class WHERE relname !~ '^(pg|sql)_' AND relkind = 'r' ORDER BY relname";
926  auto result = dbi_conn_query (conn, query);
927  bool retval = false;
928  if (result)
929  {
930  retval = dbi_result_get_numrows (result) > 0;
931  dbi_result_free (result);
932  }
933  return retval;
934 }
935 
936 
945 template <DbType Type> void
947 {
948  auto conn = dynamic_cast<GncDbiSqlConnection*>(m_conn);
949 
950  g_return_if_fail (conn != nullptr);
951  g_return_if_fail (book != nullptr);
952 
953  ENTER ("book=%p, primary=%p", book, m_book);
954  if (!conn->begin_transaction())
955  {
956  LEAVE("Failed to obtain a transaction.");
957  return;
958  }
959  if (!conn->table_operation (TableOpType::backup))
960  {
961  conn->rollback_transaction();
962  LEAVE ("Failed to rename tables");
963  return;
964  }
965  if (!conn->drop_indexes())
966  {
967  conn->rollback_transaction();
968  LEAVE ("Failed to drop indexes");
969  return;
970  }
971 
972  sync(m_book);
973  if (check_error())
974  {
975  conn->rollback_transaction();
976  LEAVE ("Failed to create new database tables");
977  return;
978  }
979  conn->table_operation (TableOpType::drop_backup);
980  conn->commit_transaction();
981  LEAVE ("book=%p", m_book);
982 }
983 /* MySQL commits the transaction and all savepoints after the first CREATE
984  * TABLE, crashing when we try to RELEASE SAVEPOINT because the savepoint
985  * doesn't exist after the commit. We must run without a wrapping transaction in
986  * that case.
987  */
988 template <> void
990 {
991  auto conn = dynamic_cast<GncDbiSqlConnection*>(m_conn);
992 
993  g_return_if_fail (conn != nullptr);
994  g_return_if_fail (book != nullptr);
995 
996  ENTER ("book=%p, primary=%p", book, m_book);
997  if (!conn->table_operation (TableOpType::backup))
998  {
999  set_error(ERR_BACKEND_SERVER_ERR);
1000  conn->table_operation (TableOpType::rollback);
1001  LEAVE ("Failed to rename tables");
1002  return;
1003  }
1004  if (!conn->drop_indexes())
1005  {
1006  conn->table_operation (TableOpType::rollback);
1007  set_error (ERR_BACKEND_SERVER_ERR);
1008  set_message("Failed to drop indexes");
1009  LEAVE ("Failed to drop indexes");
1010  return;
1011  }
1012 
1013  sync(m_book);
1014  if (check_error())
1015  {
1016  conn->table_operation (TableOpType::rollback);
1017  LEAVE ("Failed to create new database tables");
1018  return;
1019  }
1020  conn->table_operation (TableOpType::drop_backup);
1021  LEAVE ("book=%p", m_book);
1022 }
1023 /* ================================================================= */
1024 
1025 /*
1026  * Checks to see whether the file is an sqlite file or not
1027  *
1028  */
1029 template<> bool
1031 {
1032  FILE* f;
1033  gchar buf[51]{};
1034  G_GNUC_UNUSED size_t chars_read;
1035  gint status;
1036  gchar* filename;
1037 
1038  // BAD if the path is null
1039  g_return_val_if_fail (uri != nullptr, FALSE);
1040 
1041  filename = gnc_uri_get_path (uri);
1042  f = g_fopen (filename, "r");
1043  g_free (filename);
1044 
1045  // OK if the file doesn't exist - new file
1046  if (f == nullptr)
1047  {
1048  PINFO ("doesn't exist (errno=%d) -> DBI", errno);
1049  return TRUE;
1050  }
1051 
1052  // OK if file has the correct header
1053  chars_read = fread (buf, sizeof (buf) - 1, 1, f);
1054  status = fclose (f);
1055  if (status < 0)
1056  {
1057  PERR ("Error in fclose(): %d\n", errno);
1058  }
1059  if (g_str_has_prefix (buf, "SQLite format 3"))
1060  {
1061  PINFO ("has SQLite format string -> DBI");
1062  return TRUE;
1063  }
1064  PINFO ("exists, does not have SQLite format string -> not DBI");
1065 
1066  // Otherwise, BAD
1067  return FALSE;
1068 }
1069 
1070 void
1072 {
1073  const char* driver_dir;
1074  int num_drivers;
1075  gboolean have_sqlite3_driver = FALSE;
1076  gboolean have_mysql_driver = FALSE;
1077  gboolean have_pgsql_driver = FALSE;
1078 
1079  /* Initialize libdbi and see which drivers are available. Only register qof backends which
1080  have drivers available. */
1081  driver_dir = g_getenv ("GNC_DBD_DIR");
1082  if (driver_dir == nullptr)
1083  {
1084  PINFO ("GNC_DBD_DIR not set: using libdbi built-in default\n");
1085  }
1086 
1087  /* dbi_initialize returns -1 in case of errors */
1088 #if HAVE_LIBDBI_R
1089  if (dbi_instance)
1090  return;
1091  num_drivers = dbi_initialize_r (driver_dir, &dbi_instance);
1092 #else
1093  num_drivers = dbi_initialize (driver_dir);
1094 #endif
1095  if (num_drivers <= 0)
1096  {
1097 #if HAVE_LIBDBI_R
1098  if (dbi_instance)
1099  return;
1100 #endif
1101  gchar *libdir = gnc_path_get_libdir ();
1102  gchar *dir = g_build_filename (libdir, "dbd", nullptr);
1103  g_free (libdir);
1104 #if HAVE_LIBDBI_R
1105  num_drivers = dbi_initialize_r (dir, &dbi_instance);
1106 #else
1107  num_drivers = dbi_initialize (dir);
1108 #endif
1109  g_free (dir);
1110  }
1111  if (num_drivers <= 0)
1112  {
1113  PWARN ("No DBD drivers found\n");
1114  }
1115  else
1116  {
1117  dbi_driver driver = nullptr;
1118  PINFO ("%d DBD drivers found\n", num_drivers);
1119 
1120  do
1121  {
1122 #if HAVE_LIBDBI_R
1123  driver = dbi_driver_list_r (driver, dbi_instance);
1124 #else
1125  driver = dbi_driver_list (driver);
1126 #endif
1127 
1128  if (driver != nullptr)
1129  {
1130  const gchar* name = dbi_driver_get_name (driver);
1131 
1132  PINFO ("Driver: %s\n", name);
1133  if (strcmp (name, "sqlite3") == 0)
1134  {
1135  have_sqlite3_driver = TRUE;
1136  }
1137  else if (strcmp (name, "mysql") == 0)
1138  {
1139  have_mysql_driver = TRUE;
1140  }
1141  else if (strcmp (name, "pgsql") == 0)
1142  {
1143  have_pgsql_driver = TRUE;
1144  }
1145  }
1146  }
1147  while (driver != nullptr);
1148  }
1149 
1150  if (have_sqlite3_driver)
1151  {
1152  const char* name = "GnuCash Libdbi (SQLITE3) Backend";
1153  auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_SQLITE>{name, FILE_URI_TYPE});
1154  qof_backend_register_provider(std::move(prov));
1155  prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_SQLITE>{name, SQLITE3_URI_TYPE});
1156  qof_backend_register_provider(std::move(prov));
1157  }
1158 
1159  if (have_mysql_driver)
1160  {
1161  const char *name = "GnuCash Libdbi (MYSQL) Backend";
1162  auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_MYSQL>{name, "mysql"});
1163  qof_backend_register_provider(std::move(prov));
1164  }
1165 
1166  if (have_pgsql_driver)
1167  {
1168  const char* name = "GnuCash Libdbi (POSTGRESQL) Backend";
1169  auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_PGSQL>{name, "postgres"});
1170  qof_backend_register_provider(std::move(prov));
1171  }
1172 
1173  /* If needed, set log level to DEBUG so that SQl statements will be put into
1174  the gnucash.trace file. */
1175  /* qof_log_set_level( log_module, QOF_LOG_DEBUG ); */
1176 }
1177 
1178 #ifndef GNC_NO_LOADABLE_MODULES
1179 G_MODULE_EXPORT void
1181 {
1183 }
1184 
1185 G_MODULE_EXPORT void
1186 qof_backend_module_finalize (void)
1187 {
1189 }
1190 #endif /* GNC_NO_LOADABLE_MODULES */
1191 
1192 void
1194 {
1195 #if HAVE_LIBDBI_R
1196  if (dbi_instance)
1197  {
1198  dbi_shutdown_r (dbi_instance);
1199  dbi_instance = nullptr;
1200  }
1201 #else
1202  dbi_shutdown ();
1203 #endif
1204 }
1205 
1206 /* --------------------------------------------------------- */
1207 
1208 static void
1209 log_failed_field(dbi_result result, const char* fieldname)
1210 {
1211  auto idx = dbi_result_get_field_idx(result, fieldname);
1212  if (dbi_result_field_is_null_idx(result, idx))
1213  PERR("Result field %s is NULL", fieldname);
1214  else
1215  {
1216  auto type = dbi_result_get_field_type_idx(result, idx);
1217  auto attribs = dbi_result_get_field_attribs_idx(result, idx);
1218  PERR("Result field %s has type %d and attribs %d",
1219  fieldname, type, attribs);
1220  }
1221 }
1222 
1232 static GncDbiTestResult
1233 dbi_library_test (dbi_conn conn)
1234 {
1235  int64_t testlonglong = -9223372036854775807LL, resultlonglong = 0;
1236  uint64_t testulonglong = 9223372036854775807LLU, resultulonglong = 0;
1237  double testdouble = 1.7976921348623157E+307, resultdouble = 0.0;
1238  dbi_result result;
1239  GncDbiTestResult retval = GNC_DBI_PASS;
1240 
1241  result = dbi_conn_query (conn, "CREATE TEMPORARY TABLE numtest "
1242  "( test_int BIGINT, test_unsigned BIGINT,"
1243  " test_double FLOAT8 )");
1244  if (result == nullptr)
1245  {
1246  PWARN ("Test_DBI_Library: Create table failed");
1247  return GNC_DBI_FAIL_SETUP;
1248  }
1249  dbi_result_free (result);
1250  std::stringstream querystr;
1251  querystr << "INSERT INTO numtest VALUES (" << testlonglong <<
1252  ", " << testulonglong << ", " << std::setprecision(12) <<
1253  testdouble << ")";
1254  auto query = querystr.str();
1255  result = dbi_conn_query (conn, query.c_str());
1256  if (result == nullptr)
1257  {
1258  PWARN ("Test_DBI_Library: Failed to insert test row into table");
1259  return GNC_DBI_FAIL_SETUP;
1260  }
1261  dbi_result_free (result);
1262  auto locale = gnc_push_locale (LC_NUMERIC, "C");
1263  result = dbi_conn_query (conn, "SELECT * FROM numtest");
1264  if (result == nullptr || !dbi_result_get_numrows(result))
1265  {
1266  const char* errmsg;
1267  dbi_conn_error (conn, &errmsg);
1268  PWARN ("Test_DBI_Library: Failed to retrieve test row into table: %s",
1269  errmsg);
1270  dbi_conn_query (conn, "DROP TABLE numtest");
1271  gnc_pop_locale (LC_NUMERIC, locale);
1272  return GNC_DBI_FAIL_SETUP;
1273  }
1274  while (dbi_result_next_row (result))
1275  {
1276  resultlonglong = dbi_result_get_longlong (result, "test_int");
1277  if (!resultlonglong)
1278  log_failed_field(result, "test_int");
1279  resultulonglong = dbi_result_get_ulonglong (result, "test_unsigned");
1280  if (!resultulonglong)
1281  log_failed_field(result, "test_unsigned");
1282  resultdouble = dbi_result_get_double (result, "test_double");
1283  if (!resultdouble)
1284  log_failed_field(result, "test_double");
1285  }
1286  dbi_conn_query (conn, "DROP TABLE numtest");
1287  gnc_pop_locale (LC_NUMERIC, locale);
1288  if (testlonglong != resultlonglong)
1289  {
1290  PWARN ("Test_DBI_Library: LongLong Failed %" PRId64 " != % " PRId64,
1291  testlonglong, resultlonglong);
1292  retval = GNC_DBI_FAIL_TEST;
1293  }
1294  if (testulonglong != resultulonglong)
1295  {
1296  PWARN ("Test_DBI_Library: Unsigned longlong Failed %" PRIu64 " != %"
1297  PRIu64, testulonglong, resultulonglong);
1298  retval = GNC_DBI_FAIL_TEST;
1299  }
1300  /* A bug in libdbi stores only 7 digits of precision */
1301  if (testdouble >= resultdouble + 0.000001e307 ||
1302  testdouble <= resultdouble - 0.000001e307)
1303  {
1304  PWARN ("Test_DBI_Library: Double Failed %17e != %17e",
1305  testdouble, resultdouble);
1306  retval = GNC_DBI_FAIL_TEST;
1307  }
1308  return retval;
1309 }
1310 
1311 template <DbType Type> bool
1313 {
1314  auto result = dbi_library_test (conn);
1315  switch (result)
1316  {
1317  case GNC_DBI_PASS:
1318  break;
1319 
1320  case GNC_DBI_FAIL_SETUP:
1321  set_error(ERR_SQL_DBI_UNTESTABLE);
1322  set_message ("DBI library large number test incomplete");
1323  break;
1324 
1325  case GNC_DBI_FAIL_TEST:
1326  set_error (ERR_SQL_BAD_DBI);
1327  set_message ("DBI library fails large number test");
1328  break;
1329  }
1330  return result == GNC_DBI_PASS;
1331 }
1332 
1333 /* ========================== END OF FILE ===================== */
Lost connection to server.
Definition: qofbackend.h:65
QofBackend * create_backend(void)
Return a new, fully initialized backend.
load and save data to SQL via libdbi
void set_dbi_error(QofBackendError error, unsigned int repeat, bool retry) noexcept
FIXME: Just a pass-through to m_conn:
not found / no such file
Definition: qofbackend.h:92
#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
void qof_backend_register_provider(QofBackendProvider_ptr &&prov)
Let the system know about a new provider of backends.
Definition: qofsession.cpp:90
void qof_backend_set_error(QofBackend *qof_be, QofBackendError err)
Set the error on the specified QofBackend.
void gnc_features_set_used(QofBook *book, const gchar *feature)
Indicate that the current book uses the given feature.
bool type_check(const char *type)
Distinguish two providers with same access method.
database is old and needs upgrading
Definition: qofbackend.h:113
gchar * gnc_uri_get_path(const gchar *uri)
Extracts the path part from a uri.
G_MODULE_EXPORT void qof_backend_module_init(void)
This is the standardized initialization function of a qof_backend GModule, but compiling this can be ...
Can&#39;t parse url.
Definition: qofbackend.h:62
Create a new store at the URI.
Definition: qofsession.h:126
void gnc_uri_get_components(const gchar *uri, gchar **scheme, gchar **hostname, gint32 *port, gchar **username, gchar **password, gchar **path)
Converts a uri in separate components.
void load(QofBook *, QofBackendLoadType) override
Load the contents of an SQL database into a book.
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
void gnc_module_finalize_backend_dbi(void)
Shutdown function which can be used when this module is statically linked into the application...
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
error in response from server
Definition: qofbackend.h:71
void safe_sync(QofBook *) override
Safely resave a database by renaming all of its tables, recreating everything, and then dropping the ...
database is newer, we can&#39;t write to it
Definition: qofbackend.h:114
Account handling public routines.
void load(QofBook *, QofBackendLoadType) override
Load the minimal set of application data needed for the application to be operable at initial startup...
void gnc_module_init_backend_dbi(void)
Initialization function which can be used when this module is statically linked into the application...
Anchor Scheduled Transaction info in a book.
undetermined error
Definition: qofbackend.h:79
LibDBI has numeric errors.
Definition: qofbackend.h:116
All type declarations for the whole Gnucash engine.
gchar * gnc_build_translog_path(const gchar *filename)
Make a path to filename in the translog subdirectory of the user&#39;s configuration directory.
the named database doesn&#39;t exist
Definition: qofbackend.h:63
Generic api to store and retrieve preferences.
Encapsulate a libdbi dbi_conn connection.
API for the transaction logger.
SessionOpenMode
Mode for opening sessions.
Definition: qofsession.h:120
File exists, data would be destroyed.
Definition: qofbackend.h:67
void xaccLogSetBaseName(const char *basepath)
The xaccLogSetBaseName() method sets the base filepath and the root part of the journal file name...
Definition: TransLog.cpp:119
bad dbname/login/passwd or network failure
Definition: qofbackend.h:64
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
Utility functions for convert uri in separate components and back.
could not complete test for LibDBI bug
Definition: qofbackend.h:117
void session_begin(QofSession *, const char *, SessionOpenMode) override
Open the file or connect to the server.
File path resolution utility functions.
Open will fail if the URI doesn&#39;t exist or is locked.
Definition: qofsession.h:124
Utility functions for file access.