GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-sql-backend.cpp
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
65static 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
71using StrVec = std::vector<std::string>;
72
73static std::string empty_string{};
74static 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
81GncSqlBackend::GncSqlBackend(GncSqlConnection *conn, QofBook* book) :
82 QofBackend {}, m_conn{conn}, m_book{book}, m_loading{false},
83 m_in_query{false}, m_is_pristine_db{false}
84{
85 if (conn != nullptr)
86 connect (conn);
87}
88
89GncSqlBackend::~GncSqlBackend()
90{
91 connect(nullptr);
92}
93
94void
96{
97 if (m_conn != nullptr && m_conn != conn)
98 delete m_conn;
99 finalize_version_info();
100 m_conn = conn;
101}
102
103GncSqlStatementPtr
104GncSqlBackend::create_statement_from_sql(const std::string& str) const noexcept
105{
106 auto stmt = m_conn ? m_conn->create_statement_from_sql(str) : nullptr;
107 if (stmt == nullptr)
108 {
109 PERR ("SQL error: %s\n", str.c_str());
111 }
112 return stmt;
113}
114
116GncSqlBackend::execute_select_statement(const GncSqlStatementPtr& stmt) const noexcept
117{
118 auto result = m_conn ? m_conn->execute_select_statement(stmt) : nullptr;
119 if (result == nullptr)
120 {
121 PERR ("SQL error: %s\n", stmt->to_sql());
123 }
124 return result;
125}
126
127int
128GncSqlBackend::execute_nonselect_statement(const GncSqlStatementPtr& stmt) const noexcept
129{
130 int result = m_conn ? m_conn->execute_nonselect_statement(stmt) : -1;
131 if (result == -1)
132 {
133 PERR ("SQL error: %s\n", stmt->to_sql());
135 }
136 return result;
137}
138
139std::string
140GncSqlBackend::quote_string(const std::string& str) const noexcept
141{
142 g_return_val_if_fail (m_conn != nullptr, empty_string);
143 if (!m_conn)
144 return empty_string;
145 return m_conn->quote_string(str);
146}
147
148bool
149GncSqlBackend::create_table(const std::string& table_name,
150 const EntryVec& col_table) const noexcept
151{
152 g_return_val_if_fail (m_conn != nullptr, false);
153
154 ColVec info_vec;
155
156 for (auto const& table_row : col_table)
157 {
158 table_row->add_to_table (info_vec);
159 }
160 return m_conn->create_table (table_name, info_vec);
161
162}
163
164bool
165GncSqlBackend::create_table(const std::string& table_name, int table_version,
166 const EntryVec& col_table) noexcept
167{
168 if (create_table (table_name, col_table))
169 return set_table_version (table_name, table_version);
170 return false;
171}
172
173bool
174GncSqlBackend::create_index(const std::string& index_name,
175 const std::string& table_name,
176 const EntryVec& col_table) const noexcept
177{
178 g_return_val_if_fail (m_conn != nullptr, false);
179 return m_conn->create_index(index_name, table_name, col_table);
180}
181
182bool
183GncSqlBackend::add_columns_to_table(const std::string& table_name,
184 const EntryVec& col_table) const noexcept
185{
186 g_return_val_if_fail (m_conn != nullptr, false);
187
188 ColVec info_vec;
189
190 for (auto const& table_row : col_table)
191 {
192 table_row->add_to_table (info_vec);
193 }
194 return m_conn->add_columns_to_table(table_name, info_vec);
195}
196
197void
198GncSqlBackend::update_progress(double pct) const noexcept
199{
200 if (m_percentage != nullptr)
201 (m_percentage) (nullptr, pct);
202}
203
204void
205GncSqlBackend::finish_progress() const noexcept
206{
207 if (m_percentage != nullptr)
208 (m_percentage) (nullptr, -1.0);
209}
210
211void
213{
214 for(auto entry : m_backend_registry)
215 {
216 update_progress(101.0);
217 std::get<1>(entry)->create_tables(this);
218 }
219}
220
221/* Main object load order */
222static 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 */
226static const StrVec business_fixed_load_order =
227{ GNC_ID_BILLTERM, GNC_ID_TAXTABLE, GNC_ID_INVOICE };
228
229void
230GncSqlBackend::ObjectBackendRegistry::load_remaining(GncSqlBackend* sql_be)
231{
232
233 auto num_types = m_registry.size();
234 auto num_done = fixed_load_order.size() + business_fixed_load_order.size();
235
236 for (const auto& entry : m_registry)
237 {
238 std::string type;
239 GncSqlObjectBackendPtr obe = nullptr;
240 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 if (std::find(fixed_load_order.begin(), fixed_load_order.end(),
246 type) != fixed_load_order.end()) continue;
247 if (std::find(business_fixed_load_order.begin(),
248 business_fixed_load_order.end(),
249 type) != business_fixed_load_order.end()) continue;
250
251 num_done++;
252 sql_be->update_progress(num_done * 100 / num_types);
253 obe->load_all (sql_be);
254 }
255}
256
257typedef struct
258{
259 QofIdType searchObj;
260 gpointer pCompiledQuery;
262
263/* callback structure */
264typedef 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;
274
275static void
276scrub_txn_callback (QofInstance* inst, [[maybe_unused]] void* data)
277{
278 auto trans = GNC_TRANSACTION(inst);
279 xaccTransBeginEdit(trans);
280 xaccTransCommitEdit(trans);
281}
282
283void
284GncSqlBackend::load (QofBook* book, QofBackendLoadType loadType)
285{
286 Account* root;
287
288 g_return_if_fail (book != NULL);
289
290 ENTER ("sql_be=%p, book=%p", this, book);
291
292 m_loading = TRUE;
293
294 if (loadType == LOAD_TYPE_INITIAL_LOAD)
295 {
296 assert (m_book == nullptr);
297 m_book = book;
298
299 auto num_types = m_backend_registry.size();
300 auto num_done = 0;
301
302 /* Load any initial stuff. Some of this needs to happen in a certain order */
303 for (const auto& type : fixed_load_order)
304 {
305 num_done++;
306 auto obe = m_backend_registry.get_object_backend(type);
307 if (obe)
308 {
309 update_progress(num_done * 100 / num_types);
310 obe->load_all(this);
311 }
312 }
313 for (const auto& type : business_fixed_load_order)
314 {
315 num_done++;
316 auto obe = m_backend_registry.get_object_backend(type);
317 if (obe)
318 {
319 update_progress(num_done * 100 / num_types);
320 obe->load_all(this);
321 }
322 }
323
324 root = gnc_book_get_root_account( book );
325 gnc_account_foreach_descendant(root, (AccountCb)xaccAccountBeginEdit,
326 nullptr);
327
328 m_backend_registry.load_remaining(this);
329
330 gnc_account_foreach_descendant(root, (AccountCb)xaccAccountCommitEdit,
331 nullptr);
332 }
333 else if (loadType == LOAD_TYPE_LOAD_ALL)
334 {
335 // Load all transactions
336 auto obe = m_backend_registry.get_object_backend (GNC_ID_TRANS);
337 obe->load_all (this);
338 }
339
340 m_loading = FALSE;
341 std::for_each(m_postload_commodities.begin(), m_postload_commodities.end(),
342 [](gnc_commodity* comm) {
343 gnc_commodity_begin_edit(comm);
344 gnc_commodity_commit_edit(comm);
345 });
346 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 */
352 auto transactions = qof_book_get_collection (book, GNC_ID_TRANS);
353 qof_collection_foreach(transactions, scrub_txn_callback, nullptr);
355
356 /* Mark the session as clean -- though it should never be marked
357 * dirty with this backend
358 */
360 finish_progress();
361
362 LEAVE ("");
363}
364
365/* ================================================================= */
366
367bool
368GncSqlBackend::write_account_tree(Account* root)
369{
370 GList* descendants;
371 GList* node;
372 bool is_ok = true;
373
374 g_return_val_if_fail (root != nullptr, false);
375
376 auto obe = m_backend_registry.get_object_backend(GNC_ID_ACCOUNT);
377 is_ok = obe->commit (this, QOF_INSTANCE (root));
378 if (is_ok)
379 {
380 descendants = gnc_account_get_descendants (root);
381 for (node = descendants; node != NULL && is_ok; node = g_list_next (node))
382 {
383 is_ok = obe->commit(this, QOF_INSTANCE (GNC_ACCOUNT (node->data)));
384 if (!is_ok) break;
385 }
386 g_list_free (descendants);
387 }
388 update_progress(101.0);
389
390 return is_ok;
391}
392
393bool
394GncSqlBackend::write_accounts()
395{
396 update_progress(101.0);
397 auto is_ok = write_account_tree (gnc_book_get_root_account (m_book));
398 if (is_ok)
399 {
400 update_progress(101.0);
401 is_ok = write_account_tree (gnc_book_get_template_root(m_book));
402 }
403
404 return is_ok;
405}
406
407static gboolean // Can't be bool because of signature for xaccAccountTreeForEach
408write_tx (Transaction* tx, gpointer data)
409{
410 auto s = static_cast<write_objects_t*>(data);
411
412 g_return_val_if_fail (tx != NULL, 0);
413 g_return_val_if_fail (data != NULL, 0);
414
415 s->commit (QOF_INSTANCE (tx));
416 auto splitbe = s->be->get_object_backend(GNC_ID_SPLIT);
417 for (auto split_node = xaccTransGetSplitList (tx);
418 split_node != nullptr && s->is_ok;
419 split_node = g_list_next (split_node))
420 {
421 s->is_ok = splitbe->commit(s->be, QOF_INSTANCE(split_node->data));
422 }
423 s->be->update_progress (101.0);
424 return (s->is_ok ? 0 : 1);
425}
426
427bool
428GncSqlBackend::write_transactions()
429{
430 auto obe = m_backend_registry.get_object_backend(GNC_ID_TRANS);
431 write_objects_t data{this, TRUE, obe.get()};
432
433 (void)xaccAccountTreeForEachTransaction (
434 gnc_book_get_root_account (m_book), write_tx, &data);
435 update_progress(101.0);
436 return data.is_ok;
437}
438
439bool
440GncSqlBackend::write_template_transactions()
441{
442 auto obe = m_backend_registry.get_object_backend(GNC_ID_TRANS);
443 write_objects_t data{this, true, obe.get()};
445 if (gnc_account_n_descendants (ra) > 0)
446 {
447 (void)xaccAccountTreeForEachTransaction (ra, write_tx, &data);
448 update_progress(101.0);
449 }
450
451 return data.is_ok;
452}
453
454bool
455GncSqlBackend::write_schedXactions()
456{
457 GList* schedXactions;
458 SchedXaction* tmpSX;
459 bool is_ok = true;
460
461 schedXactions = gnc_book_get_schedxactions (m_book)->sx_list;
462 auto obe = m_backend_registry.get_object_backend(GNC_ID_SCHEDXACTION);
463
464 for (; schedXactions != NULL && is_ok; schedXactions = schedXactions->next)
465 {
466 tmpSX = static_cast<decltype (tmpSX)> (schedXactions->data);
467 is_ok = obe->commit (this, QOF_INSTANCE (tmpSX));
468 }
469 update_progress(101.0);
470
471 return is_ok;
472}
473
474void
476{
477 g_return_if_fail (book != NULL);
478 g_return_if_fail (m_conn != nullptr);
479
481 ENTER ("book=%p, sql_be->book=%p", book, m_book);
482 update_progress(101.0);
483
484 /* Create new tables */
485 m_is_pristine_db = true;
487
488 /* Save all contents */
489 m_book = book;
490 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 if (is_ok)
495 {
496 auto obe = m_backend_registry.get_object_backend(GNC_ID_BOOK);
497 is_ok = obe->commit (this, QOF_INSTANCE (book));
498 }
499 if (is_ok)
500 {
501 is_ok = write_accounts();
502 }
503 if (is_ok)
504 {
505 is_ok = write_transactions();
506 }
507 if (is_ok)
508 {
509 is_ok = write_template_transactions();
510 }
511 if (is_ok)
512 {
513 is_ok = write_schedXactions();
514 }
515 if (is_ok)
516 {
517 for (auto entry : m_backend_registry)
518 std::get<1>(entry)->write (this);
519 }
520 if (is_ok)
521 {
522 is_ok = m_conn->commit_transaction();
523 }
524 if (is_ok)
525 {
526 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 */
532 }
533 else
534 {
537 }
538 finish_progress();
539 LEAVE ("book=%p", book);
540}
541
542/* ================================================================= */
543/* Routines to deal with the creation of multiple books. */
544
545void
547{
548 //g_return_if_fail (inst != NULL);
549
550 //ENTER (" ");
551 //LEAVE ("");
552}
553
554void
556{
557 //g_return_if_fail (inst != NULL);
558
559 //ENTER (" ");
560 //LEAVE ("");
561}
562
563void
565{
566 m_postload_commodities.push_back(commodity);
567}
568
569GncSqlObjectBackendPtr
570GncSqlBackend::get_object_backend(const std::string& type) const noexcept
571{
572 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 */
579void
581{
582 gboolean is_dirty;
583 gboolean is_destroying;
584 gboolean is_infant;
585
586 g_return_if_fail (inst != NULL);
587 g_return_if_fail (m_conn != nullptr);
588
590 {
593 return;
594 }
595 /* During initial load where objects are being created, don't commit
596 anything, but do mark the object as clean. */
597 if (m_loading)
598 {
599 qof_instance_mark_clean (inst);
600 return;
601 }
602
603 // The engine has a PriceDB object but it isn't in the database
604 if (strcmp (inst->e_type, "PriceDB") == 0)
605 {
606 qof_instance_mark_clean (inst);
608 return;
609 }
610
611 ENTER (" ");
612
613 is_dirty = qof_instance_get_dirty_flag (inst);
614 is_destroying = qof_instance_get_destroying (inst);
615 is_infant = qof_instance_get_infant (inst);
616
617 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 if (!is_dirty && !is_destroying)
622 {
623 LEAVE ("!dirty OR !destroying");
624 return;
625 }
626
627 if (!m_conn->begin_transaction ())
628 {
629 PERR ("begin_transaction failed\n");
630 LEAVE ("Rolled back - database transaction begin error");
631 return;
632 }
633
634 bool is_ok = true;
635
636 auto obe = m_backend_registry.get_object_backend(std::string{inst->e_type});
637 if (obe != nullptr)
638 is_ok = obe->commit(this, inst);
639 else
640 {
641 PERR ("Unknown object type '%s'\n", inst->e_type);
643
644 // Don't let unknown items still mark the book as being dirty
646 qof_instance_mark_clean (inst);
647 LEAVE ("Rolled back - unknown object type");
648 return;
649 }
650 if (!is_ok)
651 {
652 // Error - roll it back
654
655 // This *should* leave things marked dirty
656 LEAVE ("Rolled back - database error");
657 return;
658 }
659
660 (void)m_conn->commit_transaction ();
661
663 qof_instance_mark_clean (inst);
664
665 LEAVE ("");
666}
667
668
675void
677{
678 g_return_if_fail (m_conn != nullptr);
679 if (m_conn->does_table_exist (VERSION_TABLE_NAME))
680 {
681 std::string sql {"SELECT * FROM "};
682 sql += VERSION_TABLE_NAME;
683 auto stmt = m_conn->create_statement_from_sql(sql);
684 auto result = m_conn->execute_select_statement (stmt);
685 for (const auto& row : *result)
686 {
687 auto name = row.get_string_at_col (TABLE_COL_NAME);
688 auto version = row.get_int_at_col (VERSION_COL_NAME);
689 if (name && version)
690 m_versions.push_back(std::make_pair(*name, static_cast<unsigned int>(*version)));
691 }
692 }
693 else
694 {
695 create_table (VERSION_TABLE_NAME, version_table);
696 set_table_version("Gnucash", gnc_prefs_get_long_version ());
697 set_table_version("Gnucash-Resave", GNUCASH_RESAVE_VERSION);
698 }
699}
700
708bool
710{
711 bool ok = create_table (VERSION_TABLE_NAME, version_table);
712 m_versions.clear();
713 set_table_version ("Gnucash", gnc_prefs_get_long_version ());
714 set_table_version ("Gnucash-Resave", GNUCASH_RESAVE_VERSION);
715 return ok;
716}
717
723void
725{
726 m_versions.clear();
727}
728
729unsigned int
730GncSqlBackend::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 if (m_is_pristine_db)
734 return 0;
735
736 auto version = std::find_if(m_versions.begin(), m_versions.end(),
737 [table_name](const VersionPair& version) {
738 return version.first == table_name; });
739 if (version != m_versions.end())
740 return version->second;
741 return 0;
742}
743
753bool
754GncSqlBackend::set_table_version (const std::string& table_name,
755 uint_t version) noexcept
756{
757 g_return_val_if_fail (version > 0, false);
758
759 unsigned int cur_version{0};
760 std::stringstream sql;
761 auto ver_entry = std::find_if(m_versions.begin(), m_versions.end(),
762 [table_name](const VersionPair& ver) {
763 return ver.first == table_name; });
764 if (ver_entry != m_versions.end())
765 cur_version = ver_entry->second;
766 if (cur_version != version)
767 {
768 if (cur_version == 0)
769 {
770 sql << "INSERT INTO " << VERSION_TABLE_NAME << " VALUES('" <<
771 table_name << "'," << version <<")";
772 m_versions.push_back(std::make_pair(table_name, version));
773 }
774 else
775 {
776 sql << "UPDATE " << VERSION_TABLE_NAME << " SET " <<
777 VERSION_COL_NAME << "=" << version << " WHERE " <<
778 TABLE_COL_NAME << "='" << table_name << "'";
779 ver_entry->second = version;
780 }
781 auto stmt = create_statement_from_sql(sql.str());
782 auto status = execute_nonselect_statement (stmt);
783 if (status == -1)
784 {
785 PERR ("SQL error: %s\n", sql.str().c_str());
787 return false;
788 }
789 }
790
791 return true;
792}
793
794void
795GncSqlBackend::upgrade_table (const std::string& table_name,
796 const EntryVec& col_table) noexcept
797{
798 DEBUG ("Upgrading %s table\n", table_name.c_str());
799
800 auto temp_table_name = table_name + "_new";
801 create_table (temp_table_name, col_table);
802 std::stringstream sql;
803 sql << "INSERT INTO " << temp_table_name << " SELECT * FROM " << table_name;
804 auto stmt = create_statement_from_sql(sql.str());
805 execute_nonselect_statement(stmt);
806
807 sql.str("");
808 sql << "DROP TABLE " << table_name;
809 stmt = create_statement_from_sql(sql.str());
810 execute_nonselect_statement(stmt);
811
812 sql.str("");
813 sql << "ALTER TABLE " << temp_table_name << " RENAME TO " << table_name;
814 stmt = create_statement_from_sql(sql.str());
815 execute_nonselect_statement(stmt);
816}
817
818static inline PairVec
819get_object_values (QofIdTypeConst obj_name,
820 gpointer pObject, const EntryVec& table)
821{
822 PairVec vec;
823
824 for (auto const& table_row : table)
825 {
826 if (!(table_row->is_autoincr()))
827 {
828 table_row->add_to_query (obj_name, pObject, vec);
829 }
830 }
831 return vec;
832}
833
834bool
835GncSqlBackend::object_in_db (const char* table_name, QofIdTypeConst obj_name,
836 const gpointer pObject, const EntryVec& table) const noexcept
837{
838 g_return_val_if_fail (table_name != nullptr, false);
839 g_return_val_if_fail (obj_name != nullptr, false);
840 g_return_val_if_fail (pObject != nullptr, false);
841
842 /* SELECT * FROM */
843 auto sql = std::string{"SELECT "} + table[0]->name() + " FROM " + table_name;
844 auto stmt = create_statement_from_sql(sql.c_str());
845 assert (stmt != nullptr);
846
847 /* WHERE */
848 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 values.resize(1);
851 stmt->add_where_cond(obj_name, values);
852 auto result = execute_select_statement (stmt);
853 return (result != nullptr && result->size() > 0);
854}
855
856bool
857GncSqlBackend::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 GncSqlStatementPtr stmt;
862
863 g_return_val_if_fail (table_name != nullptr, false);
864 g_return_val_if_fail (obj_name != nullptr, false);
865 g_return_val_if_fail (pObject != nullptr, false);
866
867 switch(op)
868 {
869 case OP_DB_INSERT:
870 stmt = build_insert_statement (table_name, obj_name, pObject, table);
871 break;
872 case OP_DB_UPDATE:
873 stmt = build_update_statement (table_name, obj_name, pObject, table);
874 break;
875 case OP_DB_DELETE:
876 stmt = build_delete_statement (table_name, obj_name, pObject, table);
877 break;
878 }
879 if (stmt == nullptr)
880 return false;
881 return (execute_nonselect_statement(stmt) != -1);
882}
883
884bool
885GncSqlBackend::save_commodity(gnc_commodity* comm) noexcept
886{
887 if (comm == nullptr) return false;
888 QofInstance* inst = QOF_INSTANCE(comm);
889 auto obe = m_backend_registry.get_object_backend(std::string(inst->e_type));
890 if (obe && !obe->instance_in_db(this, inst))
891 return obe->commit(this, inst);
892 return true;
893}
894
895GncSqlStatementPtr
896GncSqlBackend::build_insert_statement (const char* table_name,
897 QofIdTypeConst obj_name,
898 gpointer pObject,
899 const EntryVec& table) const noexcept
900{
901 GncSqlStatementPtr stmt;
902 PairVec col_values;
903 std::ostringstream sql;
904
905 g_return_val_if_fail (table_name != nullptr, nullptr);
906 g_return_val_if_fail (obj_name != nullptr, nullptr);
907 g_return_val_if_fail (pObject != nullptr, nullptr);
908 PairVec values{get_object_values(obj_name, pObject, table)};
909
910 sql << "INSERT INTO " << table_name <<"(";
911 for (auto const& col_value : values)
912 {
913 if (col_value != *values.begin())
914 sql << ",";
915 sql << col_value.first;
916 }
917
918 sql << ") VALUES(";
919 for (const auto& col_value : values)
920 {
921 if (col_value != *values.begin())
922 sql << ",";
923 sql << col_value.second;
924 }
925 sql << ")";
926
927 stmt = create_statement_from_sql(sql.str());
928 return stmt;
929}
930
931GncSqlStatementPtr
932GncSqlBackend::build_update_statement(const gchar* table_name,
933 QofIdTypeConst obj_name, gpointer pObject,
934 const EntryVec& table) const noexcept
935{
936 GncSqlStatementPtr stmt;
937 std::ostringstream sql;
938
939 g_return_val_if_fail (table_name != nullptr, nullptr);
940 g_return_val_if_fail (obj_name != nullptr, nullptr);
941 g_return_val_if_fail (pObject != nullptr, nullptr);
942
943
944 PairVec values{get_object_values (obj_name, pObject, table)};
945
946 // Create the SQL statement
947 sql << "UPDATE " << table_name << " SET ";
948
949 for (auto const& col_value : values)
950 {
951 if (col_value != *values.begin())
952 sql << ",";
953 sql << col_value.first << "=" <<
954 col_value.second;
955 }
956
957 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 values.erase(values.begin() + 1, values.end());
962 stmt->add_where_cond(obj_name, values);
963 return stmt;
964}
965
966GncSqlStatementPtr
967GncSqlBackend::build_delete_statement(const gchar* table_name,
968 QofIdTypeConst obj_name,
969 gpointer pObject,
970 const EntryVec& table) const noexcept
971{
972 std::ostringstream sql;
973
974 g_return_val_if_fail (table_name != nullptr, nullptr);
975 g_return_val_if_fail (obj_name != nullptr, nullptr);
976 g_return_val_if_fail (pObject != nullptr, nullptr);
977
978 sql << "DELETE FROM " << table_name;
979 auto stmt = create_statement_from_sql (sql.str());
980
981 /* WHERE */
982 PairVec values;
983 table[0]->add_to_query (obj_name, pObject, values);
984 PairVec col_values{values[0]};
985 stmt->add_where_cond (obj_name, col_values);
986
987 return stmt;
988}
989
990GncSqlBackend::ObjectBackendRegistry::ObjectBackendRegistry()
991{
992 register_backend(std::make_shared<GncSqlBookBackend>());
993 register_backend(std::make_shared<GncSqlCommodityBackend>());
994 register_backend(std::make_shared<GncSqlAccountBackend>());
995 register_backend(std::make_shared<GncSqlBudgetBackend>());
996 register_backend(std::make_shared<GncSqlPriceBackend>());
997 register_backend(std::make_shared<GncSqlTransBackend>());
998 register_backend(std::make_shared<GncSqlSplitBackend>());
999 register_backend(std::make_shared<GncSqlSlotsBackend>());
1000 register_backend(std::make_shared<GncSqlRecurrenceBackend>());
1001 register_backend(std::make_shared<GncSqlSchedXactionBackend>());
1002 register_backend(std::make_shared<GncSqlLotsBackend>());
1003 register_backend(std::make_shared<GncSqlBillTermBackend>());
1004 register_backend(std::make_shared<GncSqlCustomerBackend>());
1005 register_backend(std::make_shared<GncSqlEmployeeBackend>());
1006 register_backend(std::make_shared<GncSqlEntryBackend>());
1007 register_backend(std::make_shared<GncSqlInvoiceBackend>());
1008 register_backend(std::make_shared<GncSqlJobBackend>());
1009 register_backend(std::make_shared<GncSqlOrderBackend>());
1010 register_backend(std::make_shared<GncSqlTaxTableBackend>());
1011 register_backend(std::make_shared<GncSqlVendorBackend>());
1012}
1013
1014void
1015GncSqlBackend::ObjectBackendRegistry::register_backend(OBEEntry&& entry) noexcept
1016{
1017 m_registry.emplace_back(entry);
1018}
1019
1020void
1021GncSqlBackend::ObjectBackendRegistry::register_backend(GncSqlObjectBackendPtr obe) noexcept
1022{
1023 m_registry.emplace_back(make_tuple(std::string{obe->type()}, obe));
1024}
1025
1026GncSqlObjectBackendPtr
1027GncSqlBackend::ObjectBackendRegistry::get_object_backend(const std::string& type) const
1028{
1029 auto entry = std::find_if(m_registry.begin(), m_registry.end(),
1030 [type](const OBEEntry& entry){
1031 return type == std::get<0>(entry);
1032 });
1033 if (entry == m_registry.end())
1034 return nullptr;
1035
1036 return std::get<1>(*entry);
1037}
Anchor Scheduled Transaction info in a book.
API for the transaction logger.
Main SQL backend structure.
void load(QofBook *, QofBackendLoadType) override
Load the contents of an SQL database into a book.
QofBook * m_book
The primary, main open book.
void commodity_for_postload_processing(gnc_commodity *)
Register a commodity to be committed after loading is complete.
uint_t get_table_version(const std::string &table_name) const noexcept
Returns the version number for a DB table.
GncSqlConnection * m_conn
SQL connection.
bool object_in_db(const char *table_name, QofIdTypeConst obj_name, const gpointer pObject, const EntryVec &table) const noexcept
Checks whether an object is in the database or not.
VersionVec m_versions
Version number for each table.
bool create_index(const std::string &index_name, const std::string &table_name, const EntryVec &col_table) const noexcept
Creates an index in the database.
bool add_columns_to_table(const std::string &table_name, const EntryVec &col_table) const noexcept
Adds one or more columns to an existing table.
void create_tables() noexcept
Create/update all tables in the database.
bool m_is_pristine_db
Are we saving to a new pristine db?
void commit(QofInstance *) override
Object editing is complete and the object should be saved.
bool create_table(const std::string &table_name, const EntryVec &col_table) const noexcept
Creates a table in the database.
void rollback(QofInstance *) override
Object editing has been cancelled.
bool m_loading
We are performing an initial load.
void finalize_version_info() noexcept
Finalizes DB table version information.
GncSqlResultPtr execute_select_statement(const GncSqlStatementPtr &stmt) const noexcept
Executes an SQL SELECT statement and returns the result rows.
void upgrade_table(const std::string &table_name, const EntryVec &col_table) noexcept
Upgrades a table to a new structure.
void connect(GncSqlConnection *conn) noexcept
Connect the backend to a GncSqlConnection.
bool do_db_operation(E_DB_OPERATION op, const char *table_name, QofIdTypeConst obj_name, gpointer pObject, const EntryVec &table) const noexcept
Performs an operation on the database.
void begin(QofInstance *) override
An object is about to be edited.
void sync(QofBook *) override
Save the contents of a book to an SQL database.
bool save_commodity(gnc_commodity *comm) noexcept
Ensure that a commodity referenced in another object is in fact saved in the database.
bool set_table_version(const std::string &table_name, uint_t version) noexcept
Registers the version for a table.
void init_version_info() noexcept
Initializes DB table version information.
bool reset_version_info() noexcept
Resets the version table information by removing all version table info.
GncSqlObjectBackendPtr get_object_backend(const std::string &type) const noexcept
Get the GncSqlObjectBackend for the indicated type.
Encapsulate the connection to the database.
virtual bool does_table_exist(const std::string &) const noexcept=0
Returns true if successful.
virtual bool begin_transaction() noexcept=0
Returns TRUE if successful, false if error.
virtual bool rollback_transaction() noexcept=0
Returns TRUE if successful, FALSE if error.
virtual bool commit_transaction() noexcept=0
Returns TRUE if successful, FALSE if error.
Pure virtual class to iterate over a query result set.
load and save accounts data to SQL
load and save accounts data to SQL
load and save data to SQL
load and save data to SQL
load and save data to SQL
Commodity handling public routines.
load and save customer data to SQL
load and save employee data to SQL
All type declarations for the whole Gnucash engine.
load and save entry data to SQL
load and save invoice data to SQL
load and save job data to SQL
load and save data to SQL
load and save order data to SQL
Generic api to store and retrieve preferences.
load and save data to SQL
a simple price database for gnucash
load and save accounts data to SQL
load and save accounts data to SQL
load and save tax table data to SQL
load and save data to SQL
load and save vendor data to SQL
Billing Term interface.
Business Invoice Interface.
Tax Table programming interface.
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
Definition Account.cpp:1516
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Definition Account.cpp:1475
GList * gnc_account_get_descendants(const Account *account)
This routine returns a flat list of all of the accounts that are descendants of the specified account...
Definition Account.cpp:3044
gint gnc_account_n_descendants(const Account *account)
Return the number of descendants of the specified account.
Definition Account.cpp:3002
void qof_backend_set_error(QofBackend *qof_be, QofBackendError err)
Set the error on the specified QofBackend.
@ ERR_BACKEND_SERVER_ERR
error in response from server
Definition qofbackend.h:71
@ ERR_BACKEND_READONLY
cannot write to file/directory
Definition qofbackend.h:68
QofCollection * qof_book_get_collection(const QofBook *book, QofIdType entity_type)
Return The table of entities of the given type.
Definition qofbook.cpp:521
gboolean qof_book_is_readonly(const QofBook *book)
Return whether the book is read only.
Definition qofbook.cpp:497
void qof_book_mark_session_saved(QofBook *book)
The qof_book_mark_saved() routine marks the book as having been saved (to a file, to a database).
Definition qofbook.cpp:383
void xaccTransCommitEdit(Transaction *trans)
The xaccTransCommitEdit() method indicates that the changes to the transaction and its splits are com...
SplitList * xaccTransGetSplitList(const Transaction *trans)
The xaccTransGetSplitList() method returns a GList of the splits in a transaction.
void xaccTransBeginEdit(Transaction *trans)
The xaccTransBeginEdit() method must be called before any changes are made to a transaction or any of...
const gchar * QofIdType
QofIdType declaration.
Definition qofid.h:80
const gchar * QofIdTypeConst
QofIdTypeConst declaration.
Definition qofid.h:82
gboolean qof_instance_get_dirty_flag(gconstpointer ptr)
Retrieve the flag that indicates whether or not this object has been modified.
gboolean qof_instance_get_destroying(gconstpointer ptr)
Retrieve the flag that indicates whether or not this object is about to be destroyed.
QofIdType e_type
Entity type.
Definition qofinstance.h:75
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
#define ENTER(format, args...)
Print a function entry debugging message.
Definition qoflog.h:272
void set_error(QofBackendError err)
Set the error value only if there isn't already an error already.
Account * gnc_book_get_template_root(const QofBook *book)
Returns the template group from the book.
Definition SX-book.cpp:65
void xaccLogEnable(void)
document me
Definition TransLog.cpp:100
void xaccLogDisable(void)
document me
Definition TransLog.cpp:96
STRUCTS.
QofBook reference.
Definition qofbook-p.hpp:47
A Query.
Definition qofquery.cpp:75
Data-passing struct for callbacks to qof_object_foreach() used in GncSqlObjectBackend::write().