Branch data Line data Source code
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 : : \********************************************************************/
21 : : /** @file gnc-backend-dbi.c
22 : : * @brief load and save data to SQL
23 : : * @author Copyright (c) 2006-2008 Phil Longstaff <plongstaff@rogers.com>
24 : : *
25 : : * This file implements the top-level QofBackend API for saving/
26 : : * restoring data to/from an SQL db using libdbi
27 : : */
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>
97 : : class QofDbiBackendProvider : public QofBackendProvider
98 : : {
99 : : public:
100 : 114 : QofDbiBackendProvider (const char* name, const char* type) :
101 : 114 : QofBackendProvider {name, type} {}
102 : : QofDbiBackendProvider(QofDbiBackendProvider&) = delete;
103 : : QofDbiBackendProvider operator=(QofDbiBackendProvider&) = delete;
104 : : QofDbiBackendProvider(QofDbiBackendProvider&&) = delete;
105 : : QofDbiBackendProvider operator=(QofDbiBackendProvider&&) = delete;
106 : 192 : ~QofDbiBackendProvider () = default;
107 : 0 : QofBackend* create_backend(void)
108 : : {
109 : 0 : return new GncDbiBackend<Type>(nullptr, nullptr);
110 : : }
111 : 0 : bool type_check(const char* type) { return true; }
112 : : };
113 : :
114 : : /* ================================================================= */
115 : : /* ================================================================= */
116 : : struct UriStrings
117 : : {
118 : 0 : UriStrings() = default;
119 : : UriStrings(const std::string& uri);
120 : 0 : ~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 : 0 : UriStrings::UriStrings(const std::string& uri)
134 : : {
135 : : gchar *scheme, *host, *username, *password, *dbname;
136 : : int portnum;
137 : 0 : gnc_uri_get_components(uri.c_str(), &scheme, &host, &portnum, &username,
138 : : &password, &dbname);
139 : 0 : m_protocol = std::string{scheme};
140 : 0 : m_host = std::string{host};
141 : 0 : if (dbname)
142 : 0 : m_dbname = std::string{dbname};
143 : 0 : if (username)
144 : 0 : m_username = std::string{username};
145 : 0 : if (password)
146 : 0 : m_password = std::string{password};
147 : 0 : m_portnum = portnum;
148 : 0 : g_free(scheme);
149 : 0 : g_free(host);
150 : 0 : g_free(username);
151 : 0 : g_free(password);
152 : 0 : g_free(dbname);
153 : 0 : }
154 : :
155 : : std::string
156 : 0 : UriStrings::basename() const noexcept
157 : : {
158 : 0 : return m_protocol + "_" + m_host + "_" + m_username + "_" + m_dbname;
159 : : }
160 : :
161 : : const char*
162 : 0 : UriStrings::dbname() const noexcept
163 : : {
164 : 0 : return m_dbname.c_str();
165 : : }
166 : :
167 : : std::string
168 : 0 : UriStrings::quote_dbname(DbType t) const noexcept
169 : : {
170 : 0 : if (m_dbname.empty())
171 : 0 : return "";
172 : 0 : const char quote = (t == DbType::DBI_MYSQL ? '`' : '"');
173 : 0 : std::string retval(1, quote);
174 : 0 : retval += m_dbname + quote;
175 : 0 : return retval;
176 : 0 : }
177 : :
178 : : static void
179 : 0 : set_options(dbi_conn conn, const PairVec& options)
180 : : {
181 : 0 : for (const auto& option : options)
182 : : {
183 : 0 : auto opt = option.first.c_str();
184 : 0 : auto val = option.second.c_str();
185 : 0 : auto result = dbi_conn_set_option(conn, opt, val);
186 : 0 : if (result < 0)
187 : : {
188 : 0 : const char *msg = nullptr;
189 : 0 : dbi_conn_error(conn, &msg);
190 : 0 : PERR("Error setting %s option to %s: %s", opt, val, msg);
191 : 0 : throw std::runtime_error(msg);
192 : : }
193 : : }
194 : 0 : }
195 : :
196 : : /**
197 : : * Sets standard db options in a dbi_conn.
198 : : *
199 : : * @param conn dbi_conn connection
200 : : * @param uri UriStrings containing the needed parameters.
201 : : * @return TRUE if successful, FALSE if error
202 : : */
203 : : template <DbType Type> bool
204 : 0 : GncDbiBackend<Type>::set_standard_connection_options (dbi_conn conn,
205 : : const UriStrings& uri)
206 : :
207 : : {
208 : 0 : PairVec options;
209 : 0 : options.push_back(std::make_pair("host", uri.m_host));
210 : 0 : options.push_back(std::make_pair("dbname", uri.m_dbname));
211 : 0 : options.push_back(std::make_pair("username", uri.m_username));
212 : 0 : options.push_back(std::make_pair("password", uri.m_password));
213 : 0 : options.push_back(std::make_pair("encoding", "UTF-8"));
214 : : try
215 : : {
216 : 0 : set_options(conn, options);
217 : 0 : auto result = dbi_conn_set_option_numeric(conn, "port", uri.m_portnum);
218 : 0 : if (result < 0)
219 : : {
220 : 0 : const char *msg = nullptr;
221 : 0 : auto err = dbi_conn_error(conn, &msg);
222 : 0 : PERR("Error (%d) setting port option to %d: %s", err, uri.m_portnum, msg);
223 : 0 : throw std::runtime_error(msg);
224 : : }
225 : : }
226 : 0 : catch (std::runtime_error& err)
227 : : {
228 : 0 : set_error (ERR_BACKEND_SERVER_ERR);
229 : 0 : return false;
230 : : }
231 : :
232 : 0 : return true;
233 : 0 : }
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 : 0 : GncDbiBackend<Type>::conn_setup (PairVec& options, UriStrings& uri)
240 : : {
241 : 0 : const char* dbstr = (Type == DbType::DBI_SQLITE ? "sqlite3" :
242 : : Type == DbType::DBI_MYSQL ? "mysql" : "pgsql");
243 : : #if HAVE_LIBDBI_R
244 : 0 : dbi_conn conn = nullptr;
245 : 0 : if (dbi_instance)
246 : 0 : conn = dbi_conn_new_r (dbstr, dbi_instance);
247 : : else
248 : 0 : PERR ("Attempt to connect with an uninitialized dbi_instance");
249 : : #else
250 : : auto conn = dbi_conn_new (dbstr);
251 : : #endif
252 : :
253 : 0 : if (conn == nullptr)
254 : : {
255 : 0 : PERR ("Unable to create %s dbi connection", dbstr);
256 : 0 : set_error (ERR_BACKEND_BAD_URL);
257 : 0 : return nullptr;
258 : : }
259 : :
260 : 0 : dbi_conn_error_handler (conn, error_handler<Type>, this);
261 : 0 : if (!uri.m_dbname.empty() &&
262 : 0 : !set_standard_connection_options(conn, uri))
263 : : {
264 : 0 : dbi_conn_close(conn);
265 : 0 : return nullptr;
266 : : }
267 : 0 : if(!options.empty())
268 : : {
269 : : try {
270 : 0 : set_options(conn, options);
271 : : }
272 : 0 : catch (std::runtime_error& err)
273 : : {
274 : 0 : dbi_conn_close(conn);
275 : 0 : set_error (ERR_BACKEND_SERVER_ERR);
276 : 0 : return nullptr;
277 : : }
278 : : }
279 : :
280 : 0 : return conn;
281 : : }
282 : :
283 : : template <DbType Type>bool
284 : 0 : 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 : 0 : dbname = "mysql";
291 : 0 : dbcreate = "CREATE DATABASE %s CHARACTER SET utf8";
292 : : }
293 : : else
294 : : {
295 : 0 : dbname = "postgres";
296 : 0 : dbcreate = "CREATE DATABASE %s WITH TEMPLATE template0 ENCODING 'UTF8'";
297 : : }
298 : 0 : PairVec options;
299 : 0 : options.push_back(std::make_pair("dbname", dbname));
300 : : try
301 : : {
302 : 0 : set_options(conn, options);
303 : : }
304 : 0 : catch (std::runtime_error& err)
305 : : {
306 : 0 : set_error (ERR_BACKEND_SERVER_ERR);
307 : 0 : return false;
308 : : }
309 : :
310 : 0 : auto result = dbi_conn_connect (conn);
311 : 0 : if (result < 0)
312 : : {
313 : 0 : PERR ("Unable to connect to %s database", dbname);
314 : 0 : set_error(ERR_BACKEND_SERVER_ERR);
315 : 0 : return false;
316 : : }
317 : : if (Type == DbType::DBI_MYSQL)
318 : 0 : adjust_sql_options(conn);
319 : 0 : auto dresult = dbi_conn_queryf (conn, dbcreate, db);
320 : 0 : if (dresult == nullptr)
321 : : {
322 : 0 : PERR ("Unable to create database '%s'\n", db);
323 : 0 : set_error (ERR_BACKEND_SERVER_ERR);
324 : 0 : return false;
325 : : }
326 : : if (Type == DbType::DBI_PGSQL)
327 : : {
328 : 0 : const char *alterdb = "ALTER DATABASE %s SET "
329 : : "standard_conforming_strings TO on";
330 : 0 : dbi_conn_queryf (conn, alterdb, db);
331 : : }
332 : 0 : dbi_conn_close(conn);
333 : 0 : conn = nullptr;
334 : 0 : return true;
335 : 0 : }
336 : :
337 : : template <> void
338 : 0 : error_handler<DbType::DBI_SQLITE> (dbi_conn conn, void* user_data)
339 : : {
340 : : const char* msg;
341 : 0 : GncDbiBackend<DbType::DBI_SQLITE> *dbi_be =
342 : : static_cast<decltype(dbi_be)>(user_data);
343 : 0 : 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 : 0 : if (err_num == DBI_ERROR_BADIDX) return;
350 : 0 : PERR ("DBI error: %s\n", msg);
351 : 0 : if (dbi_be->connected())
352 : 0 : dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, false);
353 : : }
354 : :
355 : : template <> void
356 : 0 : GncDbiBackend<DbType::DBI_SQLITE>::session_begin(QofSession* session,
357 : : const char* new_uri,
358 : : SessionOpenMode mode)
359 : : {
360 : : gboolean file_exists;
361 : 0 : PairVec options;
362 : :
363 : 0 : g_return_if_fail (session != nullptr);
364 : 0 : g_return_if_fail (new_uri != nullptr);
365 : :
366 : 0 : ENTER (" ");
367 : :
368 : : /* Remove uri type if present */
369 : 0 : auto path = gnc_uri_get_path (new_uri);
370 : 0 : std::string filepath{path};
371 : 0 : g_free(path);
372 : 0 : GFileTest ftest = static_cast<decltype (ftest)> (
373 : : G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS) ;
374 : 0 : file_exists = g_file_test (filepath.c_str(), ftest);
375 : 0 : bool create{mode == SESSION_NEW_STORE || mode == SESSION_NEW_OVERWRITE};
376 : 0 : if (!create && !file_exists)
377 : : {
378 : 0 : set_error (ERR_FILEIO_FILE_NOT_FOUND);
379 : 0 : std::string msg{"Sqlite3 file "};
380 : 0 : set_message (msg + filepath + " not found");
381 : 0 : PWARN ("Sqlite3 file %s not found", filepath.c_str());
382 : 0 : LEAVE("Error");
383 : 0 : return;
384 : 0 : }
385 : :
386 : 0 : if (create && file_exists)
387 : : {
388 : 0 : if (mode == SESSION_NEW_OVERWRITE)
389 : 0 : g_unlink (filepath.c_str());
390 : : else
391 : : {
392 : 0 : set_error (ERR_BACKEND_STORE_EXISTS);
393 : 0 : auto msg = "Might clobber, mode not SESSION_NEW_OVERWRITE";
394 : 0 : PWARN ("%s", msg);
395 : 0 : LEAVE("Error");
396 : 0 : return;
397 : : }
398 : : }
399 : :
400 : 0 : connect(nullptr);
401 : : /* dbi-sqlite3 documentation says that sqlite3 doesn't take a "host" option */
402 : 0 : options.push_back(std::make_pair("host", "localhost"));
403 : 0 : auto dirname = g_path_get_dirname (filepath.c_str());
404 : 0 : auto basename = g_path_get_basename (filepath.c_str());
405 : 0 : options.push_back(std::make_pair("dbname", basename));
406 : 0 : options.push_back(std::make_pair("sqlite3_dbdir", dirname));
407 : 0 : if (basename != nullptr) g_free (basename);
408 : 0 : if (dirname != nullptr) g_free (dirname);
409 : 0 : UriStrings uri;
410 : 0 : auto conn = conn_setup(options, uri);
411 : 0 : if (conn == nullptr)
412 : : {
413 : 0 : LEAVE("Error");
414 : 0 : return;
415 : : }
416 : :
417 : 0 : auto result = dbi_conn_connect (conn);
418 : :
419 : 0 : if (result < 0)
420 : : {
421 : 0 : dbi_conn_close(conn);
422 : 0 : PERR ("Unable to connect to %s: %d\n", new_uri, result);
423 : 0 : set_error (ERR_BACKEND_BAD_URL);
424 : 0 : LEAVE("Error");
425 : 0 : return;
426 : : }
427 : :
428 : 0 : if (!conn_test_dbi_library(conn))
429 : : {
430 : 0 : 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 : 0 : dbi_conn_close (conn);
436 : 0 : conn = nullptr;
437 : 0 : g_unlink (filepath.c_str());
438 : : }
439 : 0 : dbi_conn_close(conn);
440 : 0 : LEAVE("Bad DBI Library");
441 : 0 : return;
442 : : }
443 : :
444 : : try
445 : : {
446 : 0 : connect(new GncDbiSqlConnection(DbType::DBI_SQLITE,
447 : 0 : this, conn, mode));
448 : : }
449 : 0 : catch (std::runtime_error& err)
450 : : {
451 : 0 : return;
452 : 0 : }
453 : :
454 : : /* We should now have a proper session set up.
455 : : * Let's start logging */
456 : 0 : xaccLogSetBaseName (filepath.c_str());
457 : 0 : PINFO ("logpath=%s", filepath.c_str() ? filepath.c_str() : "(null)");
458 : 0 : LEAVE ("");
459 : 0 : }
460 : :
461 : :
462 : : template <> void
463 : 0 : error_handler<DbType::DBI_MYSQL> (dbi_conn conn, void* user_data)
464 : : {
465 : 0 : GncDbiBackend<DbType::DBI_MYSQL>* dbi_be =
466 : : static_cast<decltype(dbi_be)>(user_data);
467 : : const char* msg;
468 : :
469 : 0 : 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 : 0 : 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 : 0 : if (err_num == 1049) // Database doesn't exist
485 : : {
486 : 0 : PINFO ("DBI error: %s\n", msg);
487 : 0 : dbi_be->set_exists(false);
488 : 0 : 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 : 0 : if (!dbi_be->connected())
496 : : {
497 : 0 : PINFO ("DBI error: %s\n", msg);
498 : 0 : PINFO ("Note: GncDbiSqlConnection not yet initialized. Skipping further error processing.");
499 : 0 : return;
500 : : }
501 : :
502 : : /* Test for other errors */
503 : 0 : if (err_num == 2006) // Server has gone away
504 : : {
505 : 0 : PINFO ("DBI error: %s - Reconnecting...\n", msg);
506 : 0 : dbi_be->set_dbi_error (ERR_BACKEND_CONN_LOST, 1, true);
507 : 0 : dbi_be->retry_connection(msg);
508 : : }
509 : 0 : else if (err_num == 2003) // Unable to connect
510 : : {
511 : 0 : dbi_be->set_dbi_error (ERR_BACKEND_CANT_CONNECT, 1, true);
512 : 0 : dbi_be->retry_connection (msg);
513 : : }
514 : 0 : else if (err_num == 1007) //Database exists
515 : : {
516 : 0 : dbi_be->set_exists(true);
517 : 0 : return;
518 : : }
519 : :
520 : : else // Any other error
521 : : {
522 : 0 : PERR ("DBI error: %s\n", msg);
523 : 0 : 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 : 0 : 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 : 0 : SQL_OPTION_TO_REMOVE "\\b,?)"};
541 : 0 : return regex_replace(str, reg, std::string{""});
542 : 0 : }
543 : :
544 : : /* checks mysql sql_options and adjusts if necessary */
545 : : static void
546 : 0 : adjust_sql_options (dbi_conn connection)
547 : : {
548 : 0 : dbi_result result = dbi_conn_query( connection, "SELECT @@sql_mode");
549 : 0 : if (result == nullptr)
550 : : {
551 : : const char* errmsg;
552 : 0 : int err = dbi_conn_error(connection, &errmsg);
553 : 0 : PERR("Unable to read sql_mode %d : %s", err, errmsg);
554 : 0 : return;
555 : : }
556 : 0 : dbi_result_first_row(result);
557 : 0 : std::string str{dbi_result_get_string_idx(result, 1)};
558 : 0 : dbi_result_free(result);
559 : 0 : if (str.empty())
560 : : {
561 : : const char* errmsg;
562 : 0 : int err = dbi_conn_error(connection, &errmsg);
563 : 0 : if (err)
564 : 0 : PERR("Unable to get sql_mode %d : %s", err, errmsg);
565 : : else
566 : 0 : PINFO("Sql_mode isn't set.");
567 : 0 : return;
568 : : }
569 : 0 : PINFO("Initial sql_mode: %s", str.c_str());
570 : 0 : if(str.find(SQL_OPTION_TO_REMOVE) != std::string::npos)
571 : 0 : str = adjust_sql_options_string(str);
572 : :
573 : : //https://bugs.gnucash.org/show_bug.cgi?id=798112
574 : 0 : const char* backslash_option{"NO_BACKSLASH_ESCAPES"};
575 : :
576 : 0 : if (str.find(backslash_option) == std::string::npos)
577 : : {
578 : 0 : if (!str.empty())
579 : 0 : str.append(",");
580 : 0 : str.append(backslash_option);
581 : : }
582 : :
583 : 0 : PINFO("Setting sql_mode to %s", str.c_str());
584 : 0 : std::string set_str{"SET sql_mode='" + std::move(str) + "'"};
585 : 0 : dbi_result set_result = dbi_conn_query(connection,
586 : : set_str.c_str());
587 : 0 : if (set_result)
588 : : {
589 : 0 : dbi_result_free(set_result);
590 : : }
591 : : else
592 : : {
593 : : const char* errmsg;
594 : 0 : int err = dbi_conn_error(connection, &errmsg);
595 : 0 : PERR("Unable to set sql_mode %d : %s", err, errmsg);
596 : : }
597 : 0 : }
598 : :
599 : : template <DbType Type> bool
600 : 0 : drop_database(dbi_conn conn, const UriStrings& uri)
601 : : {
602 : : const char *root_db;
603 : : if (Type == DbType::DBI_PGSQL)
604 : : {
605 : 0 : root_db = "template1";
606 : : }
607 : : else if (Type == DbType::DBI_MYSQL)
608 : : {
609 : 0 : root_db = "mysql";
610 : : }
611 : : else
612 : : {
613 : : PERR ("Unknown database type, can't proceed.");
614 : : LEAVE("Error");
615 : : return false;
616 : : }
617 : 0 : if (dbi_conn_select_db (conn, root_db) == -1)
618 : : {
619 : 0 : PERR ("Failed to switch out of %s, drop will fail.",
620 : : uri.quote_dbname(Type).c_str());
621 : 0 : LEAVE ("Error");
622 : 0 : return false;
623 : : }
624 : 0 : if (!dbi_conn_queryf (conn, "DROP DATABASE %s",
625 : : uri.quote_dbname(Type).c_str()))
626 : : {
627 : 0 : 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 : 0 : LEAVE ("Error");
631 : 0 : return false;
632 : : }
633 : 0 : return true;
634 : : }
635 : :
636 : : template <DbType Type> void
637 : 0 : GncDbiBackend<Type>::session_begin (QofSession* session, const char* new_uri,
638 : : SessionOpenMode mode)
639 : : {
640 : 0 : PairVec options;
641 : :
642 : 0 : g_return_if_fail (session != nullptr);
643 : 0 : g_return_if_fail (new_uri != nullptr);
644 : :
645 : 0 : 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 : 0 : UriStrings uri(new_uri);
651 : :
652 : : if (Type == DbType::DBI_PGSQL)
653 : : {
654 : 0 : if (uri.m_portnum == 0)
655 : 0 : 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 : 0 : auto lcname = g_utf8_strdown (uri.dbname(), -1);
661 : 0 : uri.m_dbname = std::string{lcname};
662 : 0 : g_free(lcname);
663 : : }
664 : 0 : connect(nullptr);
665 : :
666 : 0 : bool create{mode == SESSION_NEW_STORE || mode == SESSION_NEW_OVERWRITE};
667 : 0 : auto conn = conn_setup(options, uri);
668 : 0 : if (conn == nullptr)
669 : : {
670 : 0 : LEAVE("Error");
671 : 0 : return;
672 : : }
673 : :
674 : 0 : m_exists = true; //May be unset in the error handler.
675 : 0 : auto result = dbi_conn_connect (conn);
676 : 0 : if (result == 0)
677 : : {
678 : : if (Type == DbType::DBI_MYSQL)
679 : 0 : adjust_sql_options (conn);
680 : 0 : if(!conn_test_dbi_library(conn))
681 : : {
682 : 0 : dbi_conn_close(conn);
683 : 0 : LEAVE("Error");
684 : 0 : return;
685 : : }
686 : 0 : bool create = (mode == SESSION_NEW_STORE ||
687 : : mode == SESSION_NEW_OVERWRITE);
688 : 0 : if (create && save_may_clobber_data<Type>(conn, uri.quote_dbname(Type)))
689 : : {
690 : 0 : if (mode == SESSION_NEW_OVERWRITE)
691 : : {
692 : 0 : if (!drop_database<Type>(conn, uri))
693 : 0 : return;
694 : : }
695 : : else
696 : : {
697 : 0 : set_error (ERR_BACKEND_STORE_EXISTS);
698 : 0 : PWARN ("Database already exists, Might clobber it.");
699 : 0 : dbi_conn_close(conn);
700 : 0 : LEAVE("Error");
701 : 0 : return;
702 : : }
703 : : /* Drop successful. */
704 : 0 : m_exists = false;
705 : : }
706 : :
707 : : }
708 : 0 : else if (m_exists)
709 : : {
710 : 0 : PERR ("Unable to connect to database '%s'\n", uri.dbname());
711 : 0 : set_error (ERR_BACKEND_CANT_CONNECT);
712 : 0 : dbi_conn_close(conn);
713 : 0 : LEAVE("Error");
714 : 0 : return;
715 : : }
716 : 0 : else if (!create)
717 : : {
718 : 0 : PERR ("Database '%s' does not exist\n", uri.dbname());
719 : 0 : set_error(ERR_BACKEND_NO_SUCH_DB);
720 : 0 : std::string msg{"Database "};
721 : 0 : set_message(msg + uri.dbname() + " not found");
722 : 0 : LEAVE("Error");
723 : 0 : return;
724 : 0 : }
725 : :
726 : 0 : if (create)
727 : : {
728 : 0 : if (!m_exists &&
729 : 0 : !create_database(conn, uri.quote_dbname(Type).c_str()))
730 : : {
731 : 0 : dbi_conn_close(conn);
732 : 0 : LEAVE("Error");
733 : 0 : return;
734 : : }
735 : 0 : conn = conn_setup(options, uri);
736 : 0 : result = dbi_conn_connect (conn);
737 : 0 : if (result < 0)
738 : : {
739 : 0 : PERR ("Unable to create database '%s'\n", uri.dbname());
740 : 0 : set_error (ERR_BACKEND_SERVER_ERR);
741 : 0 : dbi_conn_close(conn);
742 : 0 : LEAVE("Error");
743 : 0 : return;
744 : : }
745 : : if (Type == DbType::DBI_MYSQL)
746 : 0 : adjust_sql_options (conn);
747 : 0 : if (!conn_test_dbi_library(conn))
748 : : {
749 : : if (Type == DbType::DBI_PGSQL)
750 : 0 : dbi_conn_select_db (conn, "template1");
751 : 0 : dbi_conn_queryf (conn, "DROP DATABASE %s",
752 : : uri.quote_dbname(Type).c_str());
753 : 0 : dbi_conn_close(conn);
754 : 0 : return;
755 : : }
756 : : }
757 : :
758 : 0 : connect(nullptr);
759 : : try
760 : : {
761 : 0 : connect(new GncDbiSqlConnection(Type, this, conn, mode));
762 : : }
763 : 0 : catch (std::runtime_error& err)
764 : : {
765 : 0 : return;
766 : : }
767 : : /* We should now have a proper session set up.
768 : : * Let's start logging */
769 : 0 : auto translog_path = gnc_build_translog_path (uri.basename().c_str());
770 : 0 : xaccLogSetBaseName (translog_path);
771 : 0 : PINFO ("logpath=%s", translog_path ? translog_path : "(null)");
772 : 0 : g_free (translog_path);
773 : :
774 : 0 : LEAVE (" ");
775 : 0 : }
776 : :
777 : : template<> void
778 : 0 : error_handler<DbType::DBI_PGSQL> (dbi_conn conn, void* user_data)
779 : : {
780 : 0 : GncDbiBackend<DbType::DBI_PGSQL>* dbi_be =
781 : : static_cast<decltype(dbi_be)>(user_data);
782 : : const char* msg;
783 : :
784 : 0 : 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 : 0 : if (err_num == DBI_ERROR_BADIDX) return;
791 : 0 : if (g_str_has_prefix (msg, "FATAL: database") &&
792 : 0 : g_str_has_suffix (msg, "does not exist\n"))
793 : : {
794 : 0 : PINFO ("DBI error: %s\n", msg);
795 : 0 : dbi_be->set_exists(false);
796 : : }
797 : 0 : else if (g_strrstr (msg,
798 : : "server closed the connection unexpectedly")) // Connection lost
799 : : {
800 : 0 : if (!dbi_be->connected())
801 : : {
802 : 0 : PWARN ("DBI Error: Connection lost, connection pointer invalid");
803 : 0 : return;
804 : : }
805 : 0 : PINFO ("DBI error: %s - Reconnecting...\n", msg);
806 : 0 : dbi_be->set_dbi_error (ERR_BACKEND_CONN_LOST, 1, true);
807 : 0 : dbi_be->retry_connection(msg);
808 : : }
809 : 0 : else if (g_str_has_prefix (msg, "connection pointer is NULL") ||
810 : 0 : g_str_has_prefix (msg, "could not connect to server")) // No connection
811 : : {
812 : :
813 : 0 : if (!dbi_be->connected())
814 : 0 : qof_backend_set_error(reinterpret_cast<QofBackend*>(dbi_be),
815 : : ERR_BACKEND_CANT_CONNECT);
816 : : else
817 : : {
818 : 0 : dbi_be->set_dbi_error(ERR_BACKEND_CANT_CONNECT, 1, true);
819 : 0 : dbi_be->retry_connection (msg);
820 : : }
821 : : }
822 : : else
823 : : {
824 : 0 : PERR ("DBI error: %s\n", msg);
825 : 0 : if (dbi_be->connected())
826 : 0 : dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, false);
827 : : }
828 : : }
829 : :
830 : : /* ================================================================= */
831 : :
832 : : template <DbType Type> void
833 : 0 : GncDbiBackend<Type>::session_end ()
834 : : {
835 : 0 : ENTER (" ");
836 : :
837 : 0 : finalize_version_info ();
838 : 0 : connect(nullptr);
839 : :
840 : 0 : LEAVE (" ");
841 : 0 : }
842 : :
843 : : template <DbType Type>
844 : 0 : GncDbiBackend<Type>::~GncDbiBackend()
845 : : {
846 : : /* Stop transaction logging */
847 : 0 : xaccLogSetBaseName (nullptr);
848 : 0 : }
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 : 0 : GncDbiBackend<Type>::load (QofBook* book, QofBackendLoadType loadType)
863 : : {
864 : 0 : g_return_if_fail (book != nullptr);
865 : :
866 : 0 : ENTER ("dbi_be=%p, book=%p", this, book);
867 : :
868 : 0 : if (loadType == LOAD_TYPE_INITIAL_LOAD)
869 : : {
870 : :
871 : : // Set up table version information
872 : 0 : init_version_info ();
873 : 0 : assert (m_book == nullptr);
874 : 0 : create_tables();
875 : : }
876 : :
877 : 0 : GncSqlBackend::load(book, loadType);
878 : :
879 : : if (Type == DbType::DBI_SQLITE)
880 : 0 : gnc_features_set_used(book, GNC_FEATURE_SQLITE3_ISO_DATES);
881 : :
882 : 0 : 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 : 0 : set_error(ERR_SQL_DB_TOO_OLD);
888 : : }
889 : 0 : 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 : 0 : set_error(ERR_SQL_DB_TOO_NEW);
896 : : }
897 : :
898 : :
899 : 0 : LEAVE ("");
900 : : }
901 : :
902 : : /* ================================================================= */
903 : : /* This is used too early to call GncDbiProvider::get_table_list(). */
904 : : template <DbType T> bool
905 : 0 : 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 : 0 : auto result = dbi_conn_get_table_list (conn, dbname.c_str(), nullptr);
910 : 0 : bool retval = false;
911 : 0 : if (result)
912 : : {
913 : 0 : retval = dbi_result_get_numrows (result) > 0;
914 : 0 : dbi_result_free (result);
915 : : }
916 : 0 : return retval;
917 : : }
918 : :
919 : : template <> bool
920 : 0 : 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 : 0 : const char* query = "SELECT relname FROM pg_class WHERE relname !~ '^(pg|sql)_' AND relkind = 'r' ORDER BY relname";
926 : 0 : auto result = dbi_conn_query (conn, query);
927 : 0 : bool retval = false;
928 : 0 : if (result)
929 : : {
930 : 0 : retval = dbi_result_get_numrows (result) > 0;
931 : 0 : dbi_result_free (result);
932 : : }
933 : 0 : return retval;
934 : : }
935 : :
936 : :
937 : : /**
938 : : * Safely resave a database by renaming all of its tables, recreating
939 : : * everything, and then dropping the backup tables only if there were
940 : : * no errors. If there are errors, drop the new tables and restore the
941 : : * originals.
942 : : *
943 : : * @param book: QofBook to be saved in the database.
944 : : */
945 : : template <DbType Type> void
946 : 0 : GncDbiBackend<Type>::safe_sync (QofBook* book)
947 : : {
948 : 0 : auto conn = dynamic_cast<GncDbiSqlConnection*>(m_conn);
949 : :
950 : 0 : g_return_if_fail (conn != nullptr);
951 : 0 : g_return_if_fail (book != nullptr);
952 : :
953 : 0 : ENTER ("book=%p, primary=%p", book, m_book);
954 : 0 : if (!conn->begin_transaction())
955 : : {
956 : 0 : LEAVE("Failed to obtain a transaction.");
957 : 0 : return;
958 : : }
959 : 0 : if (!conn->table_operation (TableOpType::backup))
960 : : {
961 : 0 : conn->rollback_transaction();
962 : 0 : LEAVE ("Failed to rename tables");
963 : 0 : return;
964 : : }
965 : 0 : if (!conn->drop_indexes())
966 : : {
967 : 0 : conn->rollback_transaction();
968 : 0 : LEAVE ("Failed to drop indexes");
969 : 0 : return;
970 : : }
971 : :
972 : 0 : sync(m_book);
973 : 0 : if (check_error())
974 : : {
975 : 0 : conn->rollback_transaction();
976 : 0 : LEAVE ("Failed to create new database tables");
977 : 0 : return;
978 : : }
979 : 0 : conn->table_operation (TableOpType::drop_backup);
980 : 0 : conn->commit_transaction();
981 : 0 : 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
989 : 0 : GncDbiBackend<DbType::DBI_MYSQL>::safe_sync (QofBook* book)
990 : : {
991 : 0 : auto conn = dynamic_cast<GncDbiSqlConnection*>(m_conn);
992 : :
993 : 0 : g_return_if_fail (conn != nullptr);
994 : 0 : g_return_if_fail (book != nullptr);
995 : :
996 : 0 : ENTER ("book=%p, primary=%p", book, m_book);
997 : 0 : if (!conn->table_operation (TableOpType::backup))
998 : : {
999 : 0 : set_error(ERR_BACKEND_SERVER_ERR);
1000 : 0 : conn->table_operation (TableOpType::rollback);
1001 : 0 : LEAVE ("Failed to rename tables");
1002 : 0 : return;
1003 : : }
1004 : 0 : if (!conn->drop_indexes())
1005 : : {
1006 : 0 : conn->table_operation (TableOpType::rollback);
1007 : 0 : set_error (ERR_BACKEND_SERVER_ERR);
1008 : 0 : set_message("Failed to drop indexes");
1009 : 0 : LEAVE ("Failed to drop indexes");
1010 : 0 : return;
1011 : : }
1012 : :
1013 : 0 : sync(m_book);
1014 : 0 : if (check_error())
1015 : : {
1016 : 0 : conn->table_operation (TableOpType::rollback);
1017 : 0 : LEAVE ("Failed to create new database tables");
1018 : 0 : return;
1019 : : }
1020 : 0 : conn->table_operation (TableOpType::drop_backup);
1021 : 0 : 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
1030 : 2 : QofDbiBackendProvider<DbType::DBI_SQLITE>::type_check(const char *uri)
1031 : : {
1032 : : FILE* f;
1033 : 2 : 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 : 2 : g_return_val_if_fail (uri != nullptr, FALSE);
1040 : :
1041 : 2 : filename = gnc_uri_get_path (uri);
1042 : 2 : f = g_fopen (filename, "r");
1043 : 2 : g_free (filename);
1044 : :
1045 : : // OK if the file doesn't exist - new file
1046 : 2 : if (f == nullptr)
1047 : : {
1048 : 0 : PINFO ("doesn't exist (errno=%d) -> DBI", errno);
1049 : 0 : return TRUE;
1050 : : }
1051 : :
1052 : : // OK if file has the correct header
1053 : 2 : chars_read = fread (buf, sizeof (buf) - 1, 1, f);
1054 : 2 : status = fclose (f);
1055 : 2 : if (status < 0)
1056 : : {
1057 : 0 : PERR ("Error in fclose(): %d\n", errno);
1058 : : }
1059 : 2 : if (g_str_has_prefix (buf, "SQLite format 3"))
1060 : : {
1061 : 0 : PINFO ("has SQLite format string -> DBI");
1062 : 0 : return TRUE;
1063 : : }
1064 : 2 : PINFO ("exists, does not have SQLite format string -> not DBI");
1065 : :
1066 : : // Otherwise, BAD
1067 : 2 : return FALSE;
1068 : : }
1069 : :
1070 : : void
1071 : 57 : gnc_module_init_backend_dbi (void)
1072 : : {
1073 : : const char* driver_dir;
1074 : : int num_drivers;
1075 : 57 : gboolean have_sqlite3_driver = FALSE;
1076 : 57 : gboolean have_mysql_driver = FALSE;
1077 : 57 : 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 : 57 : driver_dir = g_getenv ("GNC_DBD_DIR");
1082 : 57 : if (driver_dir == nullptr)
1083 : : {
1084 : 57 : 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 : 57 : if (dbi_instance)
1090 : 0 : return;
1091 : 57 : num_drivers = dbi_initialize_r (driver_dir, &dbi_instance);
1092 : : #else
1093 : : num_drivers = dbi_initialize (driver_dir);
1094 : : #endif
1095 : 57 : if (num_drivers <= 0)
1096 : : {
1097 : : #if HAVE_LIBDBI_R
1098 : 0 : if (dbi_instance)
1099 : 0 : return;
1100 : : #endif
1101 : 0 : gchar *libdir = gnc_path_get_libdir ();
1102 : 0 : gchar *dir = g_build_filename (libdir, "dbd", nullptr);
1103 : 0 : g_free (libdir);
1104 : : #if HAVE_LIBDBI_R
1105 : 0 : num_drivers = dbi_initialize_r (dir, &dbi_instance);
1106 : : #else
1107 : : num_drivers = dbi_initialize (dir);
1108 : : #endif
1109 : 0 : g_free (dir);
1110 : : }
1111 : 57 : if (num_drivers <= 0)
1112 : : {
1113 : 0 : PWARN ("No DBD drivers found\n");
1114 : : }
1115 : : else
1116 : : {
1117 : 57 : dbi_driver driver = nullptr;
1118 : 57 : PINFO ("%d DBD drivers found\n", num_drivers);
1119 : :
1120 : : do
1121 : : {
1122 : : #if HAVE_LIBDBI_R
1123 : 114 : driver = dbi_driver_list_r (driver, dbi_instance);
1124 : : #else
1125 : : driver = dbi_driver_list (driver);
1126 : : #endif
1127 : :
1128 : 114 : if (driver != nullptr)
1129 : : {
1130 : 57 : const gchar* name = dbi_driver_get_name (driver);
1131 : :
1132 : 57 : PINFO ("Driver: %s\n", name);
1133 : 57 : if (strcmp (name, "sqlite3") == 0)
1134 : : {
1135 : 57 : have_sqlite3_driver = TRUE;
1136 : : }
1137 : 0 : else if (strcmp (name, "mysql") == 0)
1138 : : {
1139 : 0 : have_mysql_driver = TRUE;
1140 : : }
1141 : 0 : else if (strcmp (name, "pgsql") == 0)
1142 : : {
1143 : 0 : have_pgsql_driver = TRUE;
1144 : : }
1145 : : }
1146 : : }
1147 : 114 : while (driver != nullptr);
1148 : : }
1149 : :
1150 : 57 : if (have_sqlite3_driver)
1151 : : {
1152 : 57 : const char* name = "GnuCash Libdbi (SQLITE3) Backend";
1153 : 57 : auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_SQLITE>{name, FILE_URI_TYPE});
1154 : 57 : qof_backend_register_provider(std::move(prov));
1155 : 57 : prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_SQLITE>{name, SQLITE3_URI_TYPE});
1156 : 57 : qof_backend_register_provider(std::move(prov));
1157 : 57 : }
1158 : :
1159 : 57 : if (have_mysql_driver)
1160 : : {
1161 : 0 : const char *name = "GnuCash Libdbi (MYSQL) Backend";
1162 : 0 : auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_MYSQL>{name, "mysql"});
1163 : 0 : qof_backend_register_provider(std::move(prov));
1164 : 0 : }
1165 : :
1166 : 57 : if (have_pgsql_driver)
1167 : : {
1168 : 0 : const char* name = "GnuCash Libdbi (POSTGRESQL) Backend";
1169 : 0 : auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_PGSQL>{name, "postgres"});
1170 : 0 : qof_backend_register_provider(std::move(prov));
1171 : 0 : }
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
1180 : 58 : qof_backend_module_init (void)
1181 : : {
1182 : 58 : gnc_module_init_backend_dbi ();
1183 : 58 : }
1184 : :
1185 : : G_MODULE_EXPORT void
1186 : 0 : qof_backend_module_finalize (void)
1187 : : {
1188 : 0 : gnc_module_finalize_backend_dbi ();
1189 : 0 : }
1190 : : #endif /* GNC_NO_LOADABLE_MODULES */
1191 : :
1192 : : void
1193 : 0 : gnc_module_finalize_backend_dbi (void)
1194 : : {
1195 : : #if HAVE_LIBDBI_R
1196 : 0 : if (dbi_instance)
1197 : : {
1198 : 0 : dbi_shutdown_r (dbi_instance);
1199 : 0 : dbi_instance = nullptr;
1200 : : }
1201 : : #else
1202 : : dbi_shutdown ();
1203 : : #endif
1204 : 0 : }
1205 : :
1206 : : /* --------------------------------------------------------- */
1207 : :
1208 : : static void
1209 : 0 : log_failed_field(dbi_result result, const char* fieldname)
1210 : : {
1211 : 0 : auto idx = dbi_result_get_field_idx(result, fieldname);
1212 : 0 : if (dbi_result_field_is_null_idx(result, idx))
1213 : 0 : PERR("Result field %s is NULL", fieldname);
1214 : : else
1215 : : {
1216 : 0 : auto type = dbi_result_get_field_type_idx(result, idx);
1217 : 0 : auto attribs = dbi_result_get_field_attribs_idx(result, idx);
1218 : 0 : PERR("Result field %s has type %d and attribs %d",
1219 : : fieldname, type, attribs);
1220 : : }
1221 : 0 : }
1222 : :
1223 : : /** Users discovered a bug in some distributions of libdbi, where if
1224 : : * it is compiled on certain versions of gcc with the -ffast-math
1225 : : * compiler option it fails to correctly handle saving of 64-bit
1226 : : * values. This function tests for the problem.
1227 : : * @param: conn: The just-opened dbi_conn
1228 : : * @returns: GNC_DBI_PASS if the dbi library is safe to use,
1229 : : * GNC_DBI_FAIL_SETUP if the test could not be completed, or
1230 : : * GNC_DBI_FAIL_TEST if the bug was found.
1231 : : */
1232 : : static GncDbiTestResult
1233 : 0 : dbi_library_test (dbi_conn conn)
1234 : : {
1235 : 0 : int64_t testlonglong = -9223372036854775807LL, resultlonglong = 0;
1236 : 0 : uint64_t testulonglong = 9223372036854775807LLU, resultulonglong = 0;
1237 : 0 : double testdouble = 1.7976921348623157E+307, resultdouble = 0.0;
1238 : : dbi_result result;
1239 : 0 : GncDbiTestResult retval = GNC_DBI_PASS;
1240 : :
1241 : 0 : result = dbi_conn_query (conn, "CREATE TEMPORARY TABLE numtest "
1242 : : "( test_int BIGINT, test_unsigned BIGINT,"
1243 : : " test_double FLOAT8 )");
1244 : 0 : if (result == nullptr)
1245 : : {
1246 : 0 : PWARN ("Test_DBI_Library: Create table failed");
1247 : 0 : return GNC_DBI_FAIL_SETUP;
1248 : : }
1249 : 0 : dbi_result_free (result);
1250 : 0 : std::stringstream querystr;
1251 : 0 : querystr << "INSERT INTO numtest VALUES (" << testlonglong <<
1252 : 0 : ", " << testulonglong << ", " << std::setprecision(12) <<
1253 : 0 : testdouble << ")";
1254 : 0 : auto query = querystr.str();
1255 : 0 : result = dbi_conn_query (conn, query.c_str());
1256 : 0 : if (result == nullptr)
1257 : : {
1258 : 0 : PWARN ("Test_DBI_Library: Failed to insert test row into table");
1259 : 0 : return GNC_DBI_FAIL_SETUP;
1260 : : }
1261 : 0 : dbi_result_free (result);
1262 : 0 : auto locale = gnc_push_locale (LC_NUMERIC, "C");
1263 : 0 : result = dbi_conn_query (conn, "SELECT * FROM numtest");
1264 : 0 : if (result == nullptr || !dbi_result_get_numrows(result))
1265 : : {
1266 : : const char* errmsg;
1267 : 0 : dbi_conn_error (conn, &errmsg);
1268 : 0 : PWARN ("Test_DBI_Library: Failed to retrieve test row into table: %s",
1269 : : errmsg);
1270 : 0 : dbi_conn_query (conn, "DROP TABLE numtest");
1271 : 0 : gnc_pop_locale (LC_NUMERIC, locale);
1272 : 0 : return GNC_DBI_FAIL_SETUP;
1273 : : }
1274 : 0 : while (dbi_result_next_row (result))
1275 : : {
1276 : 0 : resultlonglong = dbi_result_get_longlong (result, "test_int");
1277 : 0 : if (!resultlonglong)
1278 : 0 : log_failed_field(result, "test_int");
1279 : 0 : resultulonglong = dbi_result_get_ulonglong (result, "test_unsigned");
1280 : 0 : if (!resultulonglong)
1281 : 0 : log_failed_field(result, "test_unsigned");
1282 : 0 : resultdouble = dbi_result_get_double (result, "test_double");
1283 : 0 : if (!resultdouble)
1284 : 0 : log_failed_field(result, "test_double");
1285 : : }
1286 : 0 : dbi_conn_query (conn, "DROP TABLE numtest");
1287 : 0 : gnc_pop_locale (LC_NUMERIC, locale);
1288 : 0 : if (testlonglong != resultlonglong)
1289 : : {
1290 : 0 : PWARN ("Test_DBI_Library: LongLong Failed %" PRId64 " != % " PRId64,
1291 : : testlonglong, resultlonglong);
1292 : 0 : retval = GNC_DBI_FAIL_TEST;
1293 : : }
1294 : 0 : if (testulonglong != resultulonglong)
1295 : : {
1296 : 0 : PWARN ("Test_DBI_Library: Unsigned longlong Failed %" PRIu64 " != %"
1297 : : PRIu64, testulonglong, resultulonglong);
1298 : 0 : retval = GNC_DBI_FAIL_TEST;
1299 : : }
1300 : : /* A bug in libdbi stores only 7 digits of precision */
1301 : 0 : if (testdouble >= resultdouble + 0.000001e307 ||
1302 : 0 : testdouble <= resultdouble - 0.000001e307)
1303 : : {
1304 : 0 : PWARN ("Test_DBI_Library: Double Failed %17e != %17e",
1305 : : testdouble, resultdouble);
1306 : 0 : retval = GNC_DBI_FAIL_TEST;
1307 : : }
1308 : 0 : return retval;
1309 : 0 : }
1310 : :
1311 : : template <DbType Type> bool
1312 : 0 : GncDbiBackend<Type>::conn_test_dbi_library(dbi_conn conn)
1313 : : {
1314 : 0 : auto result = dbi_library_test (conn);
1315 : 0 : switch (result)
1316 : : {
1317 : 0 : case GNC_DBI_PASS:
1318 : 0 : break;
1319 : :
1320 : 0 : case GNC_DBI_FAIL_SETUP:
1321 : 0 : set_error(ERR_SQL_DBI_UNTESTABLE);
1322 : 0 : set_message ("DBI library large number test incomplete");
1323 : 0 : break;
1324 : :
1325 : 0 : case GNC_DBI_FAIL_TEST:
1326 : 0 : set_error (ERR_SQL_BAD_DBI);
1327 : 0 : set_message ("DBI library fails large number test");
1328 : 0 : break;
1329 : : }
1330 : 0 : return result == GNC_DBI_PASS;
1331 : : }
1332 : :
1333 : : /* ========================== END OF FILE ===================== */
|