GnuCash c935c2f+
Loading...
Searching...
No Matches
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
76static 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
84static 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
92static void adjust_sql_options (dbi_conn connection);
93template<DbType Type> bool save_may_clobber_data (dbi_conn conn,
94 const std::string& dbname);
95
96template <DbType Type>
98{
99public:
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
133UriStrings::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
155std::string
156UriStrings::basename() const noexcept
157{
158 return m_protocol + "_" + m_host + "_" + m_username + "_" + m_dbname;
159}
160
161const char*
162UriStrings::dbname() const noexcept
163{
164 return m_dbname.c_str();
165}
166
167std::string
168UriStrings::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
178static void
179set_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
203template <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
235template <DbType Type> void error_handler(dbi_conn conn, void* data);
236void error_handler(dbi_conn conn, void* data);
237
238template <DbType Type> dbi_conn
239GncDbiBackend<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
283template <DbType Type>bool
284GncDbiBackend<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
337template <> void
338error_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
355template <> 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 (mode != SESSION_READ_ONLY && !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
462template <> void
463error_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 */
533std::string
534adjust_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 static const 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 */
545static void
546adjust_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
599template <DbType Type> bool
600drop_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
636template <DbType Type> void
637GncDbiBackend<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 (mode != SESSION_READ_ONLY && !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
777template<> void
778error_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
832template <DbType Type> void
834{
835 ENTER (" ");
836
837 finalize_version_info ();
838 connect(nullptr);
839
840 LEAVE (" ");
841}
842
843template <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 */
861template <DbType Type> void
862GncDbiBackend<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(). */
904template <DbType T> bool
905save_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
919template <> bool
920save_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
945template <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->commit_transaction();
980 conn->table_operation (TableOpType::drop_backup);
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 */
988template <> 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 */
1029template<> 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
1070void
1071gnc_module_init_backend_dbi (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 {
1100 dbi_shutdown_r (dbi_instance);
1101 dbi_instance = nullptr;
1102 }
1103#endif
1104 gchar *libdir = gnc_path_get_libdir ();
1105 gchar *dir = g_build_filename (libdir, "dbd", nullptr);
1106 g_free (libdir);
1107#if HAVE_LIBDBI_R
1108 num_drivers = dbi_initialize_r (dir, &dbi_instance);
1109#else
1110 num_drivers = dbi_initialize (dir);
1111#endif
1112 g_free (dir);
1113 }
1114 if (num_drivers <= 0)
1115 {
1116 PWARN ("No DBD drivers found\n");
1117 }
1118 else
1119 {
1120 dbi_driver driver = nullptr;
1121 PINFO ("%d DBD drivers found\n", num_drivers);
1122
1123 do
1124 {
1125#if HAVE_LIBDBI_R
1126 driver = dbi_driver_list_r (driver, dbi_instance);
1127#else
1128 driver = dbi_driver_list (driver);
1129#endif
1130
1131 if (driver != nullptr)
1132 {
1133 const gchar* name = dbi_driver_get_name (driver);
1134
1135 PINFO ("Driver: %s\n", name);
1136 if (strcmp (name, "sqlite3") == 0)
1137 {
1138 have_sqlite3_driver = TRUE;
1139 }
1140 else if (strcmp (name, "mysql") == 0)
1141 {
1142 have_mysql_driver = TRUE;
1143 }
1144 else if (strcmp (name, "pgsql") == 0)
1145 {
1146 have_pgsql_driver = TRUE;
1147 }
1148 }
1149 }
1150 while (driver != nullptr);
1151 }
1152
1153 if (have_sqlite3_driver)
1154 {
1155 const char* name = "GnuCash Libdbi (SQLITE3) Backend";
1156 auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_SQLITE>{name, FILE_URI_TYPE});
1157 qof_backend_register_provider(std::move(prov));
1158 prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_SQLITE>{name, SQLITE3_URI_TYPE});
1159 qof_backend_register_provider(std::move(prov));
1160 }
1161
1162 if (have_mysql_driver)
1163 {
1164 const char *name = "GnuCash Libdbi (MYSQL) Backend";
1165 auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_MYSQL>{name, "mysql"});
1166 qof_backend_register_provider(std::move(prov));
1167 }
1168
1169 if (have_pgsql_driver)
1170 {
1171 const char* name = "GnuCash Libdbi (POSTGRESQL) Backend";
1172 auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_PGSQL>{name, "postgres"});
1173 qof_backend_register_provider(std::move(prov));
1174 }
1175
1176 /* If needed, set log level to DEBUG so that SQl statements will be put into
1177 the gnucash.trace file. */
1178 /* qof_log_set_level( log_module, QOF_LOG_DEBUG ); */
1179}
1180
1181#ifndef GNC_NO_LOADABLE_MODULES
1182G_MODULE_EXPORT void
1183qof_backend_module_init (void)
1184{
1185 gnc_module_init_backend_dbi ();
1186}
1187
1188G_MODULE_EXPORT void
1189qof_backend_module_finalize (void)
1190{
1191 gnc_module_finalize_backend_dbi ();
1192}
1193#endif /* GNC_NO_LOADABLE_MODULES */
1194
1195void
1196gnc_module_finalize_backend_dbi (void)
1197{
1198#if HAVE_LIBDBI_R
1199 if (dbi_instance)
1200 {
1201 dbi_shutdown_r (dbi_instance);
1202 dbi_instance = nullptr;
1203 }
1204#else
1205 dbi_shutdown ();
1206#endif
1207}
1208
1209/* --------------------------------------------------------- */
1210
1211static void
1212log_failed_field(dbi_result result, const char* fieldname)
1213{
1214 auto idx = dbi_result_get_field_idx(result, fieldname);
1215 if (dbi_result_field_is_null_idx(result, idx))
1216 PERR("Result field %s is NULL", fieldname);
1217 else
1218 {
1219 auto type = dbi_result_get_field_type_idx(result, idx);
1220 auto attribs = dbi_result_get_field_attribs_idx(result, idx);
1221 PERR("Result field %s has type %d and attribs %d",
1222 fieldname, type, attribs);
1223 }
1224}
1225
1235static GncDbiTestResult
1236dbi_library_test (dbi_conn conn)
1237{
1238 int64_t testlonglong = -9223372036854775807LL, resultlonglong = 0;
1239 uint64_t testulonglong = 9223372036854775807LLU, resultulonglong = 0;
1240 double testdouble = 1.7976921348623157E+307, resultdouble = 0.0;
1241 dbi_result result;
1242 GncDbiTestResult retval = GNC_DBI_PASS;
1243
1244 result = dbi_conn_query (conn, "CREATE TEMPORARY TABLE numtest "
1245 "( test_int BIGINT, test_unsigned BIGINT,"
1246 " test_double FLOAT8 )");
1247 if (result == nullptr)
1248 {
1249 PWARN ("Test_DBI_Library: Create table failed");
1250 return GNC_DBI_FAIL_SETUP;
1251 }
1252 dbi_result_free (result);
1253 std::stringstream querystr;
1254 querystr << "INSERT INTO numtest VALUES (" << testlonglong <<
1255 ", " << testulonglong << ", " << std::setprecision(12) <<
1256 testdouble << ")";
1257 auto query = querystr.str();
1258 result = dbi_conn_query (conn, query.c_str());
1259 if (result == nullptr)
1260 {
1261 PWARN ("Test_DBI_Library: Failed to insert test row into table");
1262 return GNC_DBI_FAIL_SETUP;
1263 }
1264 dbi_result_free (result);
1265 auto locale = gnc_push_locale (LC_NUMERIC, "C");
1266 result = dbi_conn_query (conn, "SELECT * FROM numtest");
1267 if (result == nullptr || !dbi_result_get_numrows(result))
1268 {
1269 const char* errmsg;
1270 dbi_conn_error (conn, &errmsg);
1271 PWARN ("Test_DBI_Library: Failed to retrieve test row into table: %s",
1272 errmsg);
1273 dbi_conn_query (conn, "DROP TABLE numtest");
1274 gnc_pop_locale (LC_NUMERIC, locale);
1275 return GNC_DBI_FAIL_SETUP;
1276 }
1277 while (dbi_result_next_row (result))
1278 {
1279 resultlonglong = dbi_result_get_longlong (result, "test_int");
1280 if (!resultlonglong)
1281 log_failed_field(result, "test_int");
1282 resultulonglong = dbi_result_get_ulonglong (result, "test_unsigned");
1283 if (!resultulonglong)
1284 log_failed_field(result, "test_unsigned");
1285 resultdouble = dbi_result_get_double (result, "test_double");
1286 if (!resultdouble)
1287 log_failed_field(result, "test_double");
1288 }
1289 dbi_conn_query (conn, "DROP TABLE numtest");
1290 gnc_pop_locale (LC_NUMERIC, locale);
1291 if (testlonglong != resultlonglong)
1292 {
1293 PWARN ("Test_DBI_Library: LongLong Failed %" PRId64 " != % " PRId64,
1294 testlonglong, resultlonglong);
1295 retval = GNC_DBI_FAIL_TEST;
1296 }
1297 if (testulonglong != resultulonglong)
1298 {
1299 PWARN ("Test_DBI_Library: Unsigned longlong Failed %" PRIu64 " != %"
1300 PRIu64, testulonglong, resultulonglong);
1301 retval = GNC_DBI_FAIL_TEST;
1302 }
1303 /* A bug in libdbi stores only 7 digits of precision */
1304 if (testdouble >= resultdouble + 0.000001e307 ||
1305 testdouble <= resultdouble - 0.000001e307)
1306 {
1307 PWARN ("Test_DBI_Library: Double Failed %17e != %17e",
1308 testdouble, resultdouble);
1309 retval = GNC_DBI_FAIL_TEST;
1310 }
1311 return retval;
1312}
1313
1314template <DbType Type> bool
1316{
1317 auto result = dbi_library_test (conn);
1318 switch (result)
1319 {
1320 case GNC_DBI_PASS:
1321 break;
1322
1323 case GNC_DBI_FAIL_SETUP:
1324 set_error(ERR_SQL_DBI_UNTESTABLE);
1325 set_message ("DBI library large number test incomplete");
1326 break;
1327
1328 case GNC_DBI_FAIL_TEST:
1329 set_error (ERR_SQL_BAD_DBI);
1330 set_message ("DBI library fails large number test");
1331 break;
1332 }
1333 return result == GNC_DBI_PASS;
1334}
1335
1336/* ========================== END OF FILE ===================== */
Account handling public routines.
Anchor Scheduled Transaction info in a book.
API for the transaction logger.
void load(QofBook *, QofBackendLoadType) override
Load the minimal set of application data needed for the application to be operable at initial startup...
void safe_sync(QofBook *) override
Safely resave a database by renaming all of its tables, recreating everything, and then dropping the ...
void set_dbi_error(QofBackendError error, unsigned int repeat, bool retry) noexcept
FIXME: Just a pass-through to m_conn:
void session_begin(QofSession *, const char *, SessionOpenMode) override
Open the file or connect to the server.
Encapsulate a libdbi dbi_conn connection.
void load(QofBook *, QofBackendLoadType) override
Load the contents of an SQL database into a book.
bool type_check(const char *type)
Distinguish two providers with same access method.
QofBackend * create_backend(void)
Return a new, fully initialized backend.
load and save data to SQL via libdbi
All type declarations for the whole Gnucash engine.
Utility functions for file access.
File path resolution utility functions.
Generic api to store and retrieve preferences.
Utility functions for convert uri in separate components and back.
void qof_backend_set_error(QofBackend *qof_be, QofBackendError err)
Set the error on the specified QofBackend.
SessionOpenMode
Mode for opening sessions.
Definition qofsession.h:121
@ ERR_BACKEND_NO_SUCH_DB
the named database doesn't exist
Definition qofbackend.h:63
@ ERR_BACKEND_BAD_URL
Can't parse url.
Definition qofbackend.h:62
@ ERR_BACKEND_STORE_EXISTS
File exists, data would be destroyed.
Definition qofbackend.h:67
@ ERR_BACKEND_SERVER_ERR
error in response from server
Definition qofbackend.h:71
@ ERR_SQL_DB_TOO_OLD
database is old and needs upgrading
Definition qofbackend.h:113
@ ERR_SQL_DB_TOO_NEW
database is newer, we can't write to it
Definition qofbackend.h:114
@ ERR_BACKEND_CANT_CONNECT
bad dbname/login/passwd or network failure
Definition qofbackend.h:64
@ ERR_SQL_DBI_UNTESTABLE
could not complete test for LibDBI bug
Definition qofbackend.h:117
@ ERR_BACKEND_MISC
undetermined error
Definition qofbackend.h:79
@ ERR_FILEIO_FILE_NOT_FOUND
not found / no such file
Definition qofbackend.h:92
@ ERR_BACKEND_CONN_LOST
Lost connection to server.
Definition qofbackend.h:65
@ ERR_SQL_BAD_DBI
LibDBI has numeric errors.
Definition qofbackend.h:116
@ SESSION_READ_ONLY
Create a new store at the URI even if a store already exists there.
Definition qofsession.h:128
@ SESSION_NEW_STORE
Open will fail if the URI doesn't exist or is locked.
Definition qofsession.h:124
@ SESSION_NEW_OVERWRITE
Create a new store at the URI.
Definition qofsession.h:126
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250
#define ENTER(format, args...)
Print a function entry debugging message.
Definition qoflog.h:272
void xaccLogSetBaseName(const char *basepath)
The xaccLogSetBaseName() method sets the base filepath and the root part of the journal file name.
Definition TransLog.cpp:120
void gnc_features_set_used(QofBook *book, const gchar *feature)
Indicate that the current book uses the given feature.
gchar * gnc_uri_get_path(const gchar *uri)
Extracts the path part from a uri.
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.
QofBook reference.
Definition qofbook-p.hpp:47