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