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