Branch data Line data Source code
1 : : /********************************************************************
2 : : * gnc-sql-backend.cpp: Implementation of GncSqlBackend *
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 : : #include <config.h>
24 : : #include <gnc-prefs.h>
25 : : #include <gnc-engine.h>
26 : : #include <gnc-commodity.h>
27 : : #include <SX-book.h>
28 : : #include <Recurrence.h>
29 : : #include <gncBillTerm.h>
30 : : #include <gncTaxTable.h>
31 : : #include <gncInvoice.h>
32 : : #include <gnc-pricedb.h>
33 : : #include <TransLog.h>
34 : :
35 : : #include <algorithm>
36 : : #include <cassert>
37 : :
38 : : #include "gnc-sql-connection.hpp"
39 : : #include "gnc-sql-backend.hpp"
40 : : #include "gnc-sql-object-backend.hpp"
41 : : #include "gnc-sql-column-table-entry.hpp"
42 : : #include "gnc-sql-result.hpp"
43 : :
44 : : #include "gnc-account-sql.h"
45 : : #include "gnc-book-sql.h"
46 : : #include "gnc-budget-sql.h"
47 : : #include "gnc-commodity-sql.h"
48 : : #include "gnc-lots-sql.h"
49 : : #include "gnc-price-sql.h"
50 : : #include "gnc-recurrence-sql.h"
51 : : #include "gnc-schedxaction-sql.h"
52 : : #include "gnc-slots-sql.h"
53 : : #include "gnc-transaction-sql.h"
54 : :
55 : : #include "gnc-bill-term-sql.h"
56 : : #include "gnc-customer-sql.h"
57 : : #include "gnc-employee-sql.h"
58 : : #include "gnc-entry-sql.h"
59 : : #include "gnc-invoice-sql.h"
60 : : #include "gnc-job-sql.h"
61 : : #include "gnc-order-sql.h"
62 : : #include "gnc-tax-table-sql.h"
63 : : #include "gnc-vendor-sql.h"
64 : :
65 : : static QofLogModule log_module = G_LOG_DOMAIN;
66 : : #define VERSION_TABLE_NAME "versions"
67 : : #define MAX_TABLE_NAME_LEN 50
68 : : #define TABLE_COL_NAME "table_name"
69 : : #define VERSION_COL_NAME "table_version"
70 : :
71 : : using StrVec = std::vector<std::string>;
72 : :
73 : : static std::string empty_string{};
74 : : static EntryVec version_table
75 : : {
76 : : gnc_sql_make_table_entry<CT_STRING>(
77 : : TABLE_COL_NAME, MAX_TABLE_NAME_LEN, COL_PKEY | COL_NNUL),
78 : : gnc_sql_make_table_entry<CT_INT>(VERSION_COL_NAME, 0, COL_NNUL)
79 : : };
80 : :
81 : 10 : GncSqlBackend::GncSqlBackend(GncSqlConnection *conn, QofBook* book) :
82 : 10 : QofBackend {}, m_conn{conn}, m_book{book}, m_loading{false},
83 : 10 : m_in_query{false}, m_is_pristine_db{false}
84 : : {
85 : 10 : if (conn != nullptr)
86 : 1 : connect (conn);
87 : 10 : }
88 : :
89 : 10 : GncSqlBackend::~GncSqlBackend()
90 : : {
91 : 10 : connect(nullptr);
92 : 10 : }
93 : :
94 : : void
95 : 47 : GncSqlBackend::connect(GncSqlConnection *conn) noexcept
96 : : {
97 : 47 : if (m_conn != nullptr && m_conn != conn)
98 : 10 : delete m_conn;
99 : 47 : finalize_version_info();
100 : 47 : m_conn = conn;
101 : 47 : }
102 : :
103 : : GncSqlStatementPtr
104 : 542 : GncSqlBackend::create_statement_from_sql(const std::string& str) const noexcept
105 : : {
106 : 542 : auto stmt = m_conn ? m_conn->create_statement_from_sql(str) : nullptr;
107 : 542 : if (stmt == nullptr)
108 : : {
109 : 0 : PERR ("SQL error: %s\n", str.c_str());
110 : 0 : qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR);
111 : : }
112 : 542 : return stmt;
113 : : }
114 : :
115 : : GncSqlResultPtr
116 : 238 : GncSqlBackend::execute_select_statement(const GncSqlStatementPtr& stmt) const noexcept
117 : : {
118 : 238 : auto result = m_conn ? m_conn->execute_select_statement(stmt) : nullptr;
119 : 238 : if (result == nullptr)
120 : : {
121 : 0 : PERR ("SQL error: %s\n", stmt->to_sql());
122 : 0 : qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR);
123 : : }
124 : 238 : return result;
125 : : }
126 : :
127 : : int
128 : 304 : GncSqlBackend::execute_nonselect_statement(const GncSqlStatementPtr& stmt) const noexcept
129 : : {
130 : 304 : int result = m_conn ? m_conn->execute_nonselect_statement(stmt) : -1;
131 : 304 : if (result == -1)
132 : : {
133 : 0 : PERR ("SQL error: %s\n", stmt->to_sql());
134 : 0 : qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR);
135 : : }
136 : 304 : return result;
137 : : }
138 : :
139 : : std::string
140 : 0 : GncSqlBackend::quote_string(const std::string& str) const noexcept
141 : : {
142 : 0 : g_return_val_if_fail (m_conn != nullptr, empty_string);
143 : 0 : if (!m_conn)
144 : 0 : return empty_string;
145 : 0 : return m_conn->quote_string(str);
146 : : }
147 : :
148 : : bool
149 : 115 : GncSqlBackend::create_table(const std::string& table_name,
150 : : const EntryVec& col_table) const noexcept
151 : : {
152 : 115 : g_return_val_if_fail (m_conn != nullptr, false);
153 : :
154 : 115 : ColVec info_vec;
155 : :
156 : 1130 : for (auto const& table_row : col_table)
157 : : {
158 : 1015 : table_row->add_to_table (info_vec);
159 : : }
160 : 115 : return m_conn->create_table (table_name, info_vec);
161 : :
162 : 115 : }
163 : :
164 : : bool
165 : 75 : GncSqlBackend::create_table(const std::string& table_name, int table_version,
166 : : const EntryVec& col_table) noexcept
167 : : {
168 : 75 : if (create_table (table_name, col_table))
169 : 75 : return set_table_version (table_name, table_version);
170 : 0 : return false;
171 : : }
172 : :
173 : : bool
174 : 20 : GncSqlBackend::create_index(const std::string& index_name,
175 : : const std::string& table_name,
176 : : const EntryVec& col_table) const noexcept
177 : : {
178 : 20 : g_return_val_if_fail (m_conn != nullptr, false);
179 : 20 : return m_conn->create_index(index_name, table_name, col_table);
180 : : }
181 : :
182 : : bool
183 : 0 : GncSqlBackend::add_columns_to_table(const std::string& table_name,
184 : : const EntryVec& col_table) const noexcept
185 : : {
186 : 0 : g_return_val_if_fail (m_conn != nullptr, false);
187 : :
188 : 0 : ColVec info_vec;
189 : :
190 : 0 : for (auto const& table_row : col_table)
191 : : {
192 : 0 : table_row->add_to_table (info_vec);
193 : : }
194 : 0 : return m_conn->add_columns_to_table(table_name, info_vec);
195 : 0 : }
196 : :
197 : : void
198 : 335 : GncSqlBackend::update_progress(double pct) const noexcept
199 : : {
200 : 335 : if (m_percentage != nullptr)
201 : 0 : (m_percentage) (nullptr, pct);
202 : 335 : }
203 : :
204 : : void
205 : 11 : GncSqlBackend::finish_progress() const noexcept
206 : : {
207 : 11 : if (m_percentage != nullptr)
208 : 0 : (m_percentage) (nullptr, -1.0);
209 : 11 : }
210 : :
211 : : void
212 : 10 : GncSqlBackend::create_tables() noexcept
213 : : {
214 : 210 : for(auto entry : m_backend_registry)
215 : : {
216 : 200 : update_progress(101.0);
217 : 200 : std::get<1>(entry)->create_tables(this);
218 : 200 : }
219 : 10 : }
220 : :
221 : : /* Main object load order */
222 : : static const StrVec fixed_load_order
223 : : { GNC_ID_BOOK, GNC_ID_COMMODITY, GNC_ID_ACCOUNT, GNC_ID_LOT, GNC_ID_TRANS };
224 : :
225 : : /* Order in which business objects need to be loaded */
226 : : static const StrVec business_fixed_load_order =
227 : : { GNC_ID_BILLTERM, GNC_ID_TAXTABLE, GNC_ID_INVOICE };
228 : :
229 : : void
230 : 5 : GncSqlBackend::ObjectBackendRegistry::load_remaining(GncSqlBackend* sql_be)
231 : : {
232 : :
233 : 5 : auto num_types = m_registry.size();
234 : 5 : auto num_done = fixed_load_order.size() + business_fixed_load_order.size();
235 : :
236 : 105 : for (const auto& entry : m_registry)
237 : : {
238 : 100 : std::string type;
239 : 100 : GncSqlObjectBackendPtr obe = nullptr;
240 : 100 : std::tie(type, obe) = entry;
241 : :
242 : : /* Don't need to load anything if it has already been loaded with
243 : : * the fixed order.
244 : : */
245 : 100 : if (std::find(fixed_load_order.begin(), fixed_load_order.end(),
246 : 200 : type) != fixed_load_order.end()) continue;
247 : 65 : if (std::find(business_fixed_load_order.begin(),
248 : : business_fixed_load_order.end(),
249 : 130 : type) != business_fixed_load_order.end()) continue;
250 : :
251 : 50 : num_done++;
252 : 50 : sql_be->update_progress(num_done * 100 / num_types);
253 : 50 : obe->load_all (sql_be);
254 : 150 : }
255 : 5 : }
256 : :
257 : : typedef struct
258 : : {
259 : : QofIdType searchObj;
260 : : gpointer pCompiledQuery;
261 : : } gnc_sql_query_info;
262 : :
263 : : /* callback structure */
264 : : typedef struct
265 : : {
266 : : gboolean is_known;
267 : : gboolean is_ok;
268 : : GncSqlBackend* sql_be;
269 : : QofInstance* inst;
270 : : QofQuery* pQuery;
271 : : gpointer pCompiledQuery;
272 : : gnc_sql_query_info* pQueryInfo;
273 : : } sql_backend;
274 : :
275 : : static void
276 : 10 : scrub_txn_callback (QofInstance* inst, [[maybe_unused]] void* data)
277 : : {
278 : 10 : auto trans = GNC_TRANSACTION(inst);
279 : 10 : xaccTransBeginEdit(trans);
280 : 10 : xaccTransCommitEdit(trans);
281 : 10 : }
282 : :
283 : : void
284 : 6 : GncSqlBackend::load (QofBook* book, QofBackendLoadType loadType)
285 : : {
286 : : Account* root;
287 : :
288 : 6 : g_return_if_fail (book != NULL);
289 : :
290 : 6 : ENTER ("sql_be=%p, book=%p", this, book);
291 : :
292 : 6 : m_loading = TRUE;
293 : :
294 : 6 : if (loadType == LOAD_TYPE_INITIAL_LOAD)
295 : : {
296 : 5 : assert (m_book == nullptr);
297 : 5 : m_book = book;
298 : :
299 : 5 : auto num_types = m_backend_registry.size();
300 : 5 : auto num_done = 0;
301 : :
302 : : /* Load any initial stuff. Some of this needs to happen in a certain order */
303 : 30 : for (const auto& type : fixed_load_order)
304 : : {
305 : 25 : num_done++;
306 : 25 : auto obe = m_backend_registry.get_object_backend(type);
307 : 25 : if (obe)
308 : : {
309 : 25 : update_progress(num_done * 100 / num_types);
310 : 25 : obe->load_all(this);
311 : : }
312 : 25 : }
313 : 20 : for (const auto& type : business_fixed_load_order)
314 : : {
315 : 15 : num_done++;
316 : 15 : auto obe = m_backend_registry.get_object_backend(type);
317 : 15 : if (obe)
318 : : {
319 : 15 : update_progress(num_done * 100 / num_types);
320 : 15 : obe->load_all(this);
321 : : }
322 : 15 : }
323 : :
324 : 5 : root = gnc_book_get_root_account( book );
325 : 5 : gnc_account_foreach_descendant(root, (AccountCb)xaccAccountBeginEdit,
326 : : nullptr);
327 : :
328 : 5 : m_backend_registry.load_remaining(this);
329 : :
330 : 5 : gnc_account_foreach_descendant(root, (AccountCb)xaccAccountCommitEdit,
331 : : nullptr);
332 : : }
333 : 1 : else if (loadType == LOAD_TYPE_LOAD_ALL)
334 : : {
335 : : // Load all transactions
336 : 2 : auto obe = m_backend_registry.get_object_backend (GNC_ID_TRANS);
337 : 1 : obe->load_all (this);
338 : 1 : }
339 : :
340 : 6 : m_loading = FALSE;
341 : 6 : std::for_each(m_postload_commodities.begin(), m_postload_commodities.end(),
342 : 0 : [](gnc_commodity* comm) {
343 : 0 : gnc_commodity_begin_edit(comm);
344 : 0 : gnc_commodity_commit_edit(comm);
345 : 0 : });
346 : 6 : m_postload_commodities.clear();
347 : : /* We deferred the transaction scrub while loading because having
348 : : * m_loading true prevents changes from being written back to the
349 : : * database. Do that now.
350 : : */
351 : 6 : xaccLogDisable();
352 : 6 : auto transactions = qof_book_get_collection (book, GNC_ID_TRANS);
353 : 6 : qof_collection_foreach(transactions, scrub_txn_callback, nullptr);
354 : 6 : xaccLogEnable();
355 : :
356 : : /* Mark the session as clean -- though it should never be marked
357 : : * dirty with this backend
358 : : */
359 : 6 : qof_book_mark_session_saved (book);
360 : 6 : finish_progress();
361 : :
362 : 6 : LEAVE ("");
363 : : }
364 : :
365 : : /* ================================================================= */
366 : :
367 : : bool
368 : 10 : GncSqlBackend::write_account_tree(Account* root)
369 : : {
370 : : GList* descendants;
371 : : GList* node;
372 : 10 : bool is_ok = true;
373 : :
374 : 10 : g_return_val_if_fail (root != nullptr, false);
375 : :
376 : 20 : auto obe = m_backend_registry.get_object_backend(GNC_ID_ACCOUNT);
377 : 10 : is_ok = obe->commit (this, QOF_INSTANCE (root));
378 : 10 : if (is_ok)
379 : : {
380 : 10 : descendants = gnc_account_get_descendants (root);
381 : 34 : for (node = descendants; node != NULL && is_ok; node = g_list_next (node))
382 : : {
383 : 24 : is_ok = obe->commit(this, QOF_INSTANCE (GNC_ACCOUNT (node->data)));
384 : 24 : if (!is_ok) break;
385 : : }
386 : 10 : g_list_free (descendants);
387 : : }
388 : 10 : update_progress(101.0);
389 : :
390 : 10 : return is_ok;
391 : 10 : }
392 : :
393 : : bool
394 : 5 : GncSqlBackend::write_accounts()
395 : : {
396 : 5 : update_progress(101.0);
397 : 5 : auto is_ok = write_account_tree (gnc_book_get_root_account (m_book));
398 : 5 : if (is_ok)
399 : : {
400 : 5 : update_progress(101.0);
401 : 5 : is_ok = write_account_tree (gnc_book_get_template_root(m_book));
402 : : }
403 : :
404 : 5 : return is_ok;
405 : : }
406 : :
407 : : static gboolean // Can't be bool because of signature for xaccAccountTreeForEach
408 : 9 : write_tx (Transaction* tx, gpointer data)
409 : : {
410 : 9 : auto s = static_cast<write_objects_t*>(data);
411 : :
412 : 9 : g_return_val_if_fail (tx != NULL, 0);
413 : 9 : g_return_val_if_fail (data != NULL, 0);
414 : :
415 : 9 : s->commit (QOF_INSTANCE (tx));
416 : 18 : auto splitbe = s->be->get_object_backend(GNC_ID_SPLIT);
417 : 9 : for (auto split_node = xaccTransGetSplitList (tx);
418 : 27 : split_node != nullptr && s->is_ok;
419 : 0 : split_node = g_list_next (split_node))
420 : : {
421 : 18 : s->is_ok = splitbe->commit(s->be, QOF_INSTANCE(split_node->data));
422 : : }
423 : 9 : s->be->update_progress (101.0);
424 : 9 : return (s->is_ok ? 0 : 1);
425 : 9 : }
426 : :
427 : : bool
428 : 5 : GncSqlBackend::write_transactions()
429 : : {
430 : 10 : auto obe = m_backend_registry.get_object_backend(GNC_ID_TRANS);
431 : 5 : write_objects_t data{this, TRUE, obe.get()};
432 : :
433 : 5 : (void)xaccAccountTreeForEachTransaction (
434 : : gnc_book_get_root_account (m_book), write_tx, &data);
435 : 5 : update_progress(101.0);
436 : 5 : return data.is_ok;
437 : 5 : }
438 : :
439 : : bool
440 : 5 : GncSqlBackend::write_template_transactions()
441 : : {
442 : 10 : auto obe = m_backend_registry.get_object_backend(GNC_ID_TRANS);
443 : 5 : write_objects_t data{this, true, obe.get()};
444 : 5 : auto ra = gnc_book_get_template_root (m_book);
445 : 5 : if (gnc_account_n_descendants (ra) > 0)
446 : : {
447 : 1 : (void)xaccAccountTreeForEachTransaction (ra, write_tx, &data);
448 : 1 : update_progress(101.0);
449 : : }
450 : :
451 : 5 : return data.is_ok;
452 : 5 : }
453 : :
454 : : bool
455 : 5 : GncSqlBackend::write_schedXactions()
456 : : {
457 : : GList* schedXactions;
458 : : SchedXaction* tmpSX;
459 : 5 : bool is_ok = true;
460 : :
461 : 5 : schedXactions = gnc_book_get_schedxactions (m_book)->sx_list;
462 : 10 : auto obe = m_backend_registry.get_object_backend(GNC_ID_SCHEDXACTION);
463 : :
464 : 8 : for (; schedXactions != NULL && is_ok; schedXactions = schedXactions->next)
465 : : {
466 : 3 : tmpSX = static_cast<decltype (tmpSX)> (schedXactions->data);
467 : 3 : is_ok = obe->commit (this, QOF_INSTANCE (tmpSX));
468 : : }
469 : 5 : update_progress(101.0);
470 : :
471 : 5 : return is_ok;
472 : 5 : }
473 : :
474 : : void
475 : 5 : GncSqlBackend::sync(QofBook* book)
476 : : {
477 : 5 : g_return_if_fail (book != NULL);
478 : 5 : g_return_if_fail (m_conn != nullptr);
479 : :
480 : 5 : reset_version_info();
481 : 5 : ENTER ("book=%p, sql_be->book=%p", book, m_book);
482 : 5 : update_progress(101.0);
483 : :
484 : : /* Create new tables */
485 : 5 : m_is_pristine_db = true;
486 : 5 : create_tables();
487 : :
488 : : /* Save all contents */
489 : 5 : m_book = book;
490 : 5 : auto is_ok = m_conn->begin_transaction();
491 : :
492 : : // FIXME: should write the set of commodities that are used
493 : : // write_commodities(sql_be, book);
494 : 5 : if (is_ok)
495 : : {
496 : 10 : auto obe = m_backend_registry.get_object_backend(GNC_ID_BOOK);
497 : 5 : is_ok = obe->commit (this, QOF_INSTANCE (book));
498 : 5 : }
499 : 5 : if (is_ok)
500 : : {
501 : 5 : is_ok = write_accounts();
502 : : }
503 : 5 : if (is_ok)
504 : : {
505 : 5 : is_ok = write_transactions();
506 : : }
507 : 5 : if (is_ok)
508 : : {
509 : 5 : is_ok = write_template_transactions();
510 : : }
511 : 5 : if (is_ok)
512 : : {
513 : 5 : is_ok = write_schedXactions();
514 : : }
515 : 5 : if (is_ok)
516 : : {
517 : 105 : for (auto entry : m_backend_registry)
518 : 100 : std::get<1>(entry)->write (this);
519 : : }
520 : 5 : if (is_ok)
521 : : {
522 : 5 : is_ok = m_conn->commit_transaction();
523 : : }
524 : 5 : if (is_ok)
525 : : {
526 : 5 : m_is_pristine_db = false;
527 : :
528 : : /* Mark the session as clean -- though it shouldn't ever get
529 : : * marked dirty with this backend
530 : : */
531 : 5 : qof_book_mark_session_saved(book);
532 : : }
533 : : else
534 : : {
535 : 0 : set_error (ERR_BACKEND_SERVER_ERR);
536 : 0 : m_conn->rollback_transaction ();
537 : : }
538 : 5 : finish_progress();
539 : 5 : LEAVE ("book=%p", book);
540 : : }
541 : :
542 : : /* ================================================================= */
543 : : /* Routines to deal with the creation of multiple books. */
544 : :
545 : : void
546 : 377 : GncSqlBackend::begin(QofInstance* inst)
547 : : {
548 : : //g_return_if_fail (inst != NULL);
549 : :
550 : : //ENTER (" ");
551 : : //LEAVE ("");
552 : 377 : }
553 : :
554 : : void
555 : 0 : GncSqlBackend::rollback(QofInstance* inst)
556 : : {
557 : : //g_return_if_fail (inst != NULL);
558 : :
559 : : //ENTER (" ");
560 : : //LEAVE ("");
561 : 0 : }
562 : :
563 : : void
564 : 0 : GncSqlBackend::commodity_for_postload_processing(gnc_commodity* commodity)
565 : : {
566 : 0 : m_postload_commodities.push_back(commodity);
567 : 0 : }
568 : :
569 : : GncSqlObjectBackendPtr
570 : 9 : GncSqlBackend::get_object_backend(const std::string& type) const noexcept
571 : : {
572 : 9 : return m_backend_registry.get_object_backend(type);
573 : : }
574 : :
575 : :
576 : : /* Commit_edit handler - find the correct backend handler for this object
577 : : * type and call its commit handler
578 : : */
579 : : void
580 : 397 : GncSqlBackend::commit (QofInstance* inst)
581 : : {
582 : : gboolean is_dirty;
583 : : gboolean is_destroying;
584 : : gboolean is_infant;
585 : :
586 : 787 : g_return_if_fail (inst != NULL);
587 : 397 : g_return_if_fail (m_conn != nullptr);
588 : :
589 : 397 : if (qof_book_is_readonly(m_book))
590 : : {
591 : 0 : set_error (ERR_BACKEND_READONLY);
592 : 0 : (void)m_conn->rollback_transaction ();
593 : 0 : return;
594 : : }
595 : : /* During initial load where objects are being created, don't commit
596 : : anything, but do mark the object as clean. */
597 : 397 : if (m_loading)
598 : : {
599 : 375 : qof_instance_mark_clean (inst);
600 : 375 : return;
601 : : }
602 : :
603 : : // The engine has a PriceDB object but it isn't in the database
604 : 22 : if (strcmp (inst->e_type, "PriceDB") == 0)
605 : : {
606 : 0 : qof_instance_mark_clean (inst);
607 : 0 : qof_book_mark_session_saved (m_book);
608 : 0 : return;
609 : : }
610 : :
611 : 22 : ENTER (" ");
612 : :
613 : 22 : is_dirty = qof_instance_get_dirty_flag (inst);
614 : 22 : is_destroying = qof_instance_get_destroying (inst);
615 : 22 : is_infant = qof_instance_get_infant (inst);
616 : :
617 : 22 : DEBUG ("%s dirty = %d, do_free = %d, infant = %d\n",
618 : : (inst->e_type ? inst->e_type : "(null)"),
619 : : is_dirty, is_destroying, is_infant);
620 : :
621 : 22 : if (!is_dirty && !is_destroying)
622 : : {
623 : 14 : LEAVE ("!dirty OR !destroying");
624 : 14 : return;
625 : : }
626 : :
627 : 8 : if (!m_conn->begin_transaction ())
628 : : {
629 : 0 : PERR ("begin_transaction failed\n");
630 : 0 : LEAVE ("Rolled back - database transaction begin error");
631 : 0 : return;
632 : : }
633 : :
634 : 8 : bool is_ok = true;
635 : :
636 : 16 : auto obe = m_backend_registry.get_object_backend(std::string{inst->e_type});
637 : 8 : if (obe != nullptr)
638 : 7 : is_ok = obe->commit(this, inst);
639 : : else
640 : : {
641 : 1 : PERR ("Unknown object type '%s'\n", inst->e_type);
642 : 1 : (void)m_conn->rollback_transaction ();
643 : :
644 : : // Don't let unknown items still mark the book as being dirty
645 : 1 : qof_book_mark_session_saved(m_book);
646 : 1 : qof_instance_mark_clean (inst);
647 : 1 : LEAVE ("Rolled back - unknown object type");
648 : 1 : return;
649 : : }
650 : 7 : if (!is_ok)
651 : : {
652 : : // Error - roll it back
653 : 0 : (void)m_conn->rollback_transaction();
654 : :
655 : : // This *should* leave things marked dirty
656 : 0 : LEAVE ("Rolled back - database error");
657 : 0 : return;
658 : : }
659 : :
660 : 7 : (void)m_conn->commit_transaction ();
661 : :
662 : 7 : qof_book_mark_session_saved(m_book);
663 : 7 : qof_instance_mark_clean (inst);
664 : :
665 : 7 : LEAVE ("");
666 : 8 : }
667 : :
668 : :
669 : : /**
670 : : * Sees if the version table exists, and if it does, loads the info into
671 : : * the version hash table. Otherwise, it creates an empty version table.
672 : : *
673 : : * @param be Backend struct
674 : : */
675 : : void
676 : 5 : GncSqlBackend::init_version_info() noexcept
677 : : {
678 : 5 : g_return_if_fail (m_conn != nullptr);
679 : 15 : if (m_conn->does_table_exist (VERSION_TABLE_NAME))
680 : : {
681 : 5 : std::string sql {"SELECT * FROM "};
682 : 5 : sql += VERSION_TABLE_NAME;
683 : 5 : auto stmt = m_conn->create_statement_from_sql(sql);
684 : 5 : auto result = m_conn->execute_select_statement (stmt);
685 : 125 : for (const auto& row : *result)
686 : : {
687 : 120 : auto name = row.get_string_at_col (TABLE_COL_NAME);
688 : 120 : auto version = row.get_int_at_col (VERSION_COL_NAME);
689 : 120 : if (name && version)
690 : 120 : m_versions.push_back(std::make_pair(*name, static_cast<unsigned int>(*version)));
691 : 125 : }
692 : 5 : }
693 : : else
694 : : {
695 : 0 : create_table (VERSION_TABLE_NAME, version_table);
696 : 0 : set_table_version("Gnucash", gnc_prefs_get_long_version ());
697 : 0 : set_table_version("Gnucash-Resave", GNUCASH_RESAVE_VERSION);
698 : : }
699 : : }
700 : :
701 : : /**
702 : : * Resets the version table information by removing all version table info.
703 : : * It also recreates the version table in the db.
704 : : *
705 : : * @param be Backend struct
706 : : * @return TRUE if successful, FALSE if error
707 : : */
708 : : bool
709 : 5 : GncSqlBackend::reset_version_info() noexcept
710 : : {
711 : 5 : bool ok = create_table (VERSION_TABLE_NAME, version_table);
712 : 5 : m_versions.clear();
713 : 15 : set_table_version ("Gnucash", gnc_prefs_get_long_version ());
714 : 5 : set_table_version ("Gnucash-Resave", GNUCASH_RESAVE_VERSION);
715 : 5 : return ok;
716 : : }
717 : :
718 : : /**
719 : : * Finalizes the version table info by destroying the hash table.
720 : : *
721 : : * @param be Backend struct
722 : : */
723 : : void
724 : 65 : GncSqlBackend::finalize_version_info() noexcept
725 : : {
726 : 65 : m_versions.clear();
727 : 65 : }
728 : :
729 : : unsigned int
730 : 231 : GncSqlBackend::get_table_version(const std::string& table_name) const noexcept
731 : : {
732 : : /* If the db is pristine because it's being saved, the table does not exist. */
733 : 231 : if (m_is_pristine_db)
734 : 110 : return 0;
735 : :
736 : 121 : auto version = std::find_if(m_versions.begin(), m_versions.end(),
737 : 242 : [table_name](const VersionPair& version) {
738 : 1501 : return version.first == table_name; });
739 : 121 : if (version != m_versions.end())
740 : 121 : return version->second;
741 : 0 : return 0;
742 : : }
743 : :
744 : : /**
745 : : * Registers the version for a table. Registering involves updating the
746 : : * db version table and also the hash table.
747 : : *
748 : : * @param be Backend struct
749 : : * @param table_name Table name
750 : : * @param version Version number
751 : : * @return TRUE if successful, FALSE if unsuccessful
752 : : */
753 : : bool
754 : 124 : GncSqlBackend::set_table_version (const std::string& table_name,
755 : : uint_t version) noexcept
756 : : {
757 : 124 : g_return_val_if_fail (version > 0, false);
758 : :
759 : 124 : unsigned int cur_version{0};
760 : 124 : std::stringstream sql;
761 : 124 : auto ver_entry = std::find_if(m_versions.begin(), m_versions.end(),
762 : 248 : [table_name](const VersionPair& ver) {
763 : 1386 : return ver.first == table_name; });
764 : 124 : if (ver_entry != m_versions.end())
765 : 4 : cur_version = ver_entry->second;
766 : 124 : if (cur_version != version)
767 : : {
768 : 124 : if (cur_version == 0)
769 : : {
770 : : sql << "INSERT INTO " << VERSION_TABLE_NAME << " VALUES('" <<
771 : 120 : table_name << "'," << version <<")";
772 : 120 : m_versions.push_back(std::make_pair(table_name, version));
773 : : }
774 : : else
775 : : {
776 : : sql << "UPDATE " << VERSION_TABLE_NAME << " SET " <<
777 : 4 : VERSION_COL_NAME << "=" << version << " WHERE " <<
778 : 4 : TABLE_COL_NAME << "='" << table_name << "'";
779 : 4 : ver_entry->second = version;
780 : : }
781 : 124 : auto stmt = create_statement_from_sql(sql.str());
782 : 124 : auto status = execute_nonselect_statement (stmt);
783 : 124 : if (status == -1)
784 : : {
785 : 0 : PERR ("SQL error: %s\n", sql.str().c_str());
786 : 0 : qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR);
787 : 0 : return false;
788 : : }
789 : 124 : }
790 : :
791 : 124 : return true;
792 : 124 : }
793 : :
794 : : void
795 : 0 : GncSqlBackend::upgrade_table (const std::string& table_name,
796 : : const EntryVec& col_table) noexcept
797 : : {
798 : 0 : DEBUG ("Upgrading %s table\n", table_name.c_str());
799 : :
800 : 0 : auto temp_table_name = table_name + "_new";
801 : 0 : create_table (temp_table_name, col_table);
802 : 0 : std::stringstream sql;
803 : 0 : sql << "INSERT INTO " << temp_table_name << " SELECT * FROM " << table_name;
804 : 0 : auto stmt = create_statement_from_sql(sql.str());
805 : 0 : execute_nonselect_statement(stmt);
806 : :
807 : 0 : sql.str("");
808 : 0 : sql << "DROP TABLE " << table_name;
809 : 0 : stmt = create_statement_from_sql(sql.str());
810 : 0 : execute_nonselect_statement(stmt);
811 : :
812 : 0 : sql.str("");
813 : 0 : sql << "ALTER TABLE " << temp_table_name << " RENAME TO " << table_name;
814 : 0 : stmt = create_statement_from_sql(sql.str());
815 : 0 : execute_nonselect_statement(stmt);
816 : 0 : }
817 : :
818 : : static inline PairVec
819 : 211 : get_object_values (QofIdTypeConst obj_name,
820 : : gpointer pObject, const EntryVec& table)
821 : : {
822 : 211 : PairVec vec;
823 : :
824 : 2244 : for (auto const& table_row : table)
825 : : {
826 : 2033 : if (!(table_row->is_autoincr()))
827 : : {
828 : 1961 : table_row->add_to_query (obj_name, pObject, vec);
829 : : }
830 : : }
831 : 211 : return vec;
832 : : }
833 : :
834 : : bool
835 : 45 : GncSqlBackend::object_in_db (const char* table_name, QofIdTypeConst obj_name,
836 : : const gpointer pObject, const EntryVec& table) const noexcept
837 : : {
838 : 45 : g_return_val_if_fail (table_name != nullptr, false);
839 : 45 : g_return_val_if_fail (obj_name != nullptr, false);
840 : 45 : g_return_val_if_fail (pObject != nullptr, false);
841 : :
842 : : /* SELECT * FROM */
843 : 135 : auto sql = std::string{"SELECT "} + table[0]->name() + " FROM " + table_name;
844 : 45 : auto stmt = create_statement_from_sql(sql.c_str());
845 : 45 : assert (stmt != nullptr);
846 : :
847 : : /* WHERE */
848 : 45 : PairVec values{get_object_values(obj_name, pObject, table)};
849 : : /* We want only the first item in the table, which should be the PK. */
850 : 45 : values.resize(1);
851 : 45 : stmt->add_where_cond(obj_name, values);
852 : 45 : auto result = execute_select_statement (stmt);
853 : 45 : return (result != nullptr && result->size() > 0);
854 : 45 : }
855 : :
856 : : bool
857 : 179 : GncSqlBackend::do_db_operation (E_DB_OPERATION op, const char* table_name,
858 : : QofIdTypeConst obj_name, gpointer pObject,
859 : : const EntryVec& table) const noexcept
860 : : {
861 : 179 : GncSqlStatementPtr stmt;
862 : :
863 : 179 : g_return_val_if_fail (table_name != nullptr, false);
864 : 179 : g_return_val_if_fail (obj_name != nullptr, false);
865 : 179 : g_return_val_if_fail (pObject != nullptr, false);
866 : :
867 : 179 : switch(op)
868 : : {
869 : 160 : case OP_DB_INSERT:
870 : 160 : stmt = build_insert_statement (table_name, obj_name, pObject, table);
871 : 160 : break;
872 : 6 : case OP_DB_UPDATE:
873 : 6 : stmt = build_update_statement (table_name, obj_name, pObject, table);
874 : 6 : break;
875 : 13 : case OP_DB_DELETE:
876 : 13 : stmt = build_delete_statement (table_name, obj_name, pObject, table);
877 : 13 : break;
878 : : }
879 : 179 : if (stmt == nullptr)
880 : 0 : return false;
881 : 179 : return (execute_nonselect_statement(stmt) != -1);
882 : 179 : }
883 : :
884 : : bool
885 : 37 : GncSqlBackend::save_commodity(gnc_commodity* comm) noexcept
886 : : {
887 : 37 : if (comm == nullptr) return false;
888 : 37 : QofInstance* inst = QOF_INSTANCE(comm);
889 : 74 : auto obe = m_backend_registry.get_object_backend(std::string(inst->e_type));
890 : 37 : if (obe && !obe->instance_in_db(this, inst))
891 : 8 : return obe->commit(this, inst);
892 : 29 : return true;
893 : 37 : }
894 : :
895 : : GncSqlStatementPtr
896 : 160 : GncSqlBackend::build_insert_statement (const char* table_name,
897 : : QofIdTypeConst obj_name,
898 : : gpointer pObject,
899 : : const EntryVec& table) const noexcept
900 : : {
901 : 160 : GncSqlStatementPtr stmt;
902 : 160 : PairVec col_values;
903 : 160 : std::ostringstream sql;
904 : :
905 : 160 : g_return_val_if_fail (table_name != nullptr, nullptr);
906 : 160 : g_return_val_if_fail (obj_name != nullptr, nullptr);
907 : 160 : g_return_val_if_fail (pObject != nullptr, nullptr);
908 : 160 : PairVec values{get_object_values(obj_name, pObject, table)};
909 : :
910 : 160 : sql << "INSERT INTO " << table_name <<"(";
911 : 1556 : for (auto const& col_value : values)
912 : : {
913 : 1396 : if (col_value != *values.begin())
914 : 1236 : sql << ",";
915 : 1396 : sql << col_value.first;
916 : : }
917 : :
918 : 160 : sql << ") VALUES(";
919 : 1556 : for (const auto& col_value : values)
920 : : {
921 : 1396 : if (col_value != *values.begin())
922 : 1236 : sql << ",";
923 : 1396 : sql << col_value.second;
924 : : }
925 : 160 : sql << ")";
926 : :
927 : 160 : stmt = create_statement_from_sql(sql.str());
928 : 160 : return stmt;
929 : 160 : }
930 : :
931 : : GncSqlStatementPtr
932 : 6 : GncSqlBackend::build_update_statement(const gchar* table_name,
933 : : QofIdTypeConst obj_name, gpointer pObject,
934 : : const EntryVec& table) const noexcept
935 : : {
936 : 6 : GncSqlStatementPtr stmt;
937 : 6 : std::ostringstream sql;
938 : :
939 : 6 : g_return_val_if_fail (table_name != nullptr, nullptr);
940 : 6 : g_return_val_if_fail (obj_name != nullptr, nullptr);
941 : 6 : g_return_val_if_fail (pObject != nullptr, nullptr);
942 : :
943 : :
944 : 6 : PairVec values{get_object_values (obj_name, pObject, table)};
945 : :
946 : : // Create the SQL statement
947 : 6 : sql << "UPDATE " << table_name << " SET ";
948 : :
949 : 36 : for (auto const& col_value : values)
950 : : {
951 : 30 : if (col_value != *values.begin())
952 : 24 : sql << ",";
953 : 30 : sql << col_value.first << "=" <<
954 : 30 : col_value.second;
955 : : }
956 : :
957 : 6 : stmt = create_statement_from_sql(sql.str());
958 : : /* We want our where condition to be just the first column and
959 : : * value, i.e. the guid of the object.
960 : : */
961 : 6 : values.erase(values.begin() + 1, values.end());
962 : 6 : stmt->add_where_cond(obj_name, values);
963 : 6 : return stmt;
964 : 6 : }
965 : :
966 : : GncSqlStatementPtr
967 : 13 : GncSqlBackend::build_delete_statement(const gchar* table_name,
968 : : QofIdTypeConst obj_name,
969 : : gpointer pObject,
970 : : const EntryVec& table) const noexcept
971 : : {
972 : 13 : std::ostringstream sql;
973 : :
974 : 13 : g_return_val_if_fail (table_name != nullptr, nullptr);
975 : 13 : g_return_val_if_fail (obj_name != nullptr, nullptr);
976 : 13 : g_return_val_if_fail (pObject != nullptr, nullptr);
977 : :
978 : 13 : sql << "DELETE FROM " << table_name;
979 : 13 : auto stmt = create_statement_from_sql (sql.str());
980 : :
981 : : /* WHERE */
982 : 13 : PairVec values;
983 : 13 : table[0]->add_to_query (obj_name, pObject, values);
984 : 39 : PairVec col_values{values[0]};
985 : 13 : stmt->add_where_cond (obj_name, col_values);
986 : :
987 : 13 : return stmt;
988 : 26 : }
989 : :
990 : 10 : GncSqlBackend::ObjectBackendRegistry::ObjectBackendRegistry()
991 : : {
992 : 10 : register_backend(std::make_shared<GncSqlBookBackend>());
993 : 10 : register_backend(std::make_shared<GncSqlCommodityBackend>());
994 : 10 : register_backend(std::make_shared<GncSqlAccountBackend>());
995 : 10 : register_backend(std::make_shared<GncSqlBudgetBackend>());
996 : 10 : register_backend(std::make_shared<GncSqlPriceBackend>());
997 : 10 : register_backend(std::make_shared<GncSqlTransBackend>());
998 : 10 : register_backend(std::make_shared<GncSqlSplitBackend>());
999 : 10 : register_backend(std::make_shared<GncSqlSlotsBackend>());
1000 : 10 : register_backend(std::make_shared<GncSqlRecurrenceBackend>());
1001 : 10 : register_backend(std::make_shared<GncSqlSchedXactionBackend>());
1002 : 10 : register_backend(std::make_shared<GncSqlLotsBackend>());
1003 : 10 : register_backend(std::make_shared<GncSqlBillTermBackend>());
1004 : 10 : register_backend(std::make_shared<GncSqlCustomerBackend>());
1005 : 10 : register_backend(std::make_shared<GncSqlEmployeeBackend>());
1006 : 10 : register_backend(std::make_shared<GncSqlEntryBackend>());
1007 : 10 : register_backend(std::make_shared<GncSqlInvoiceBackend>());
1008 : 10 : register_backend(std::make_shared<GncSqlJobBackend>());
1009 : 10 : register_backend(std::make_shared<GncSqlOrderBackend>());
1010 : 10 : register_backend(std::make_shared<GncSqlTaxTableBackend>());
1011 : 10 : register_backend(std::make_shared<GncSqlVendorBackend>());
1012 : 10 : }
1013 : :
1014 : : void
1015 : 0 : GncSqlBackend::ObjectBackendRegistry::register_backend(OBEEntry&& entry) noexcept
1016 : : {
1017 : 0 : m_registry.emplace_back(entry);
1018 : 0 : }
1019 : :
1020 : : void
1021 : 200 : GncSqlBackend::ObjectBackendRegistry::register_backend(GncSqlObjectBackendPtr obe) noexcept
1022 : : {
1023 : 400 : m_registry.emplace_back(make_tuple(std::string{obe->type()}, obe));
1024 : 200 : }
1025 : :
1026 : : GncSqlObjectBackendPtr
1027 : 125 : GncSqlBackend::ObjectBackendRegistry::get_object_backend(const std::string& type) const
1028 : : {
1029 : 125 : auto entry = std::find_if(m_registry.begin(), m_registry.end(),
1030 : 250 : [type](const OBEEntry& entry){
1031 : 669 : return type == std::get<0>(entry);
1032 : : });
1033 : 125 : if (entry == m_registry.end())
1034 : 1 : return nullptr;
1035 : :
1036 : 124 : return std::get<1>(*entry);
1037 : : }
|