GnuCash  5.6-150-g038405b370+
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 
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 GncSqlBackend::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 
89 GncSqlBackend::~GncSqlBackend()
90 {
91  connect(nullptr);
92 }
93 
94 void
96 {
97  if (m_conn != nullptr && m_conn != conn)
98  delete m_conn;
99  finalize_version_info();
100  m_conn = conn;
101 }
102 
103 GncSqlStatementPtr
104 GncSqlBackend::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 
116 GncSqlBackend::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 
127 int
128 GncSqlBackend::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 
139 std::string
140 GncSqlBackend::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 
148 bool
149 GncSqlBackend::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 
164 bool
165 GncSqlBackend::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 
173 bool
174 GncSqlBackend::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 
182 bool
183 GncSqlBackend::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 
197 void
198 GncSqlBackend::update_progress(double pct) const noexcept
199 {
200  if (m_percentage != nullptr)
201  (m_percentage) (nullptr, pct);
202 }
203 
204 void
205 GncSqlBackend::finish_progress() const noexcept
206 {
207  if (m_percentage != nullptr)
208  (m_percentage) (nullptr, -1.0);
209 }
210 
211 void
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 */
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 GncSqlBackend::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 
257 typedef struct
258 {
259  QofIdType searchObj;
260  gpointer pCompiledQuery;
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 scrub_txn_callback (QofInstance* inst, [[maybe_unused]] void* data)
277 {
278  auto trans = GNC_TRANSACTION(inst);
279  xaccTransBeginEdit(trans);
280  xaccTransCommitEdit(trans);
281 }
282 
283 void
284 GncSqlBackend::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  */
351  xaccLogDisable();
352  auto transactions = qof_book_get_collection (book, GNC_ID_TRANS);
353  qof_collection_foreach(transactions, scrub_txn_callback, nullptr);
354  xaccLogEnable();
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 
367 bool
368 GncSqlBackend::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 
393 bool
394 GncSqlBackend::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 
407 static gboolean // Can't be bool because of signature for xaccAccountTreeForEach
408 write_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 
427 bool
428 GncSqlBackend::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 
434  gnc_book_get_root_account (m_book), write_tx, &data);
435  update_progress(101.0);
436  return data.is_ok;
437 }
438 
439 bool
440 GncSqlBackend::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 
454 bool
455 GncSqlBackend::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 
474 void
475 GncSqlBackend::sync(QofBook* book)
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;
486  create_tables();
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 
545 void
547 {
548  //g_return_if_fail (inst != NULL);
549 
550  //ENTER (" ");
551  //LEAVE ("");
552 }
553 
554 void
556 {
557  //g_return_if_fail (inst != NULL);
558 
559  //ENTER (" ");
560  //LEAVE ("");
561 }
562 
563 void
565 {
566  m_postload_commodities.push_back(commodity);
567 }
568 
569 GncSqlObjectBackendPtr
570 GncSqlBackend::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  */
579 void
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  {
592  (void)m_conn->rollback_transaction ();
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);
642  (void)m_conn->rollback_transaction ();
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
653  (void)m_conn->rollback_transaction();
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 
675 void
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 
708 bool
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 
723 void
725 {
726  m_versions.clear();
727 }
728 
729 unsigned int
730 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  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 
753 bool
754 GncSqlBackend::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 
794 void
795 GncSqlBackend::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 
818 static inline PairVec
819 get_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 
834 bool
835 GncSqlBackend::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 
856 bool
857 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  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 
884 bool
885 GncSqlBackend::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 
895 GncSqlStatementPtr
896 GncSqlBackend::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 
931 GncSqlStatementPtr
932 GncSqlBackend::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 
966 GncSqlStatementPtr
967 GncSqlBackend::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 
990 GncSqlBackend::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 
1014 void
1015 GncSqlBackend::ObjectBackendRegistry::register_backend(OBEEntry&& entry) noexcept
1016 {
1017  m_registry.emplace_back(entry);
1018 }
1019 
1020 void
1021 GncSqlBackend::ObjectBackendRegistry::register_backend(GncSqlObjectBackendPtr obe) noexcept
1022 {
1023  m_registry.emplace_back(make_tuple(std::string{obe->type()}, obe));
1024 }
1025 
1026 GncSqlObjectBackendPtr
1027 GncSqlBackend::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 }
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.
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.
bool create_table(const std::string &table_name, const EntryVec &col_table) const noexcept
Creates a table in the database.
int xaccAccountTreeForEachTransaction(Account *acc, TransactionCallback proc, void *data)
Traverse all of the transactions in the given account group.
load and save vendor data to SQL
bool set_table_version(const std::string &table_name, uint_t version) noexcept
Registers the version for a table.
GncSqlResultPtr execute_select_statement(const GncSqlStatementPtr &stmt) const noexcept
Executes an SQL SELECT statement and returns the result rows.
gint gnc_account_n_descendants(const Account *account)
Return the number of descendants of the specified account.
Definition: Account.cpp:2952
a simple price database for gnucash
#define G_LOG_DOMAIN
Functions providing the SX List as a plugin page.
load and save data to SQL
const gchar * QofIdTypeConst
QofIdTypeConst declaration.
Definition: qofid.h:82
load and save accounts data to SQL
VersionVec m_versions
Version number for each table.
STRUCTS.
void rollback(QofInstance *) override
Object editing has been cancelled.
void qof_backend_set_error(QofBackend *qof_be, QofBackendError err)
Set the error on the specified QofBackend.
void xaccLogDisable(void)
document me
Definition: TransLog.cpp:95
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
gboolean qof_instance_get_destroying(gconstpointer ptr)
Retrieve the flag that indicates whether or not this object is about to be destroyed.
void commit(QofInstance *) override
Object editing is complete and the object should be saved.
void create_tables() noexcept
Create/update all tables in the database.
load and save customer data to SQL
Account * gnc_book_get_template_root(const QofBook *book)
Returns the template group from the book.
Definition: SX-book.cpp:65
bool m_loading
We are performing an initial load.
void commodity_for_postload_processing(gnc_commodity *)
Register a commodity to be committed after loading is complete.
GncSqlConnection * m_conn
SQL connection.
load and save accounts data to SQL
load and save data to SQL
void load(QofBook *, QofBackendLoadType) override
Load the contents of an SQL database into a book.
#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 sync(QofBook *) override
Save the contents of a book to an SQL database.
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.
error in response from server
Definition: qofbackend.h:71
const gchar * QofIdType
QofIdType declaration.
Definition: qofid.h:80
load and save accounts data to SQL
load and save order data to SQL
bool save_commodity(gnc_commodity *comm) noexcept
Ensure that a commodity referenced in another object is in fact saved in the database.
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
load and save job data to SQL
load and save accounts data to SQL
void upgrade_table(const std::string &table_name, const EntryVec &col_table) noexcept
Upgrades a table to a new structure.
load and save data to SQL
gboolean qof_instance_get_dirty_flag(gconstpointer ptr)
Retrieve the flag that indicates whether or not this object has been modified.
QofBook * m_book
The primary, main open book.
Anchor Scheduled Transaction info in a book.
GncSqlObjectBackendPtr get_object_backend(const std::string &type) const noexcept
Get the GncSqlObjectBackend for the indicated type.
load and save employee data to SQL
load and save data to SQL
Tax Table programming interface.
void init_version_info() noexcept
Initializes DB table version information.
void xaccTransCommitEdit(Transaction *trans)
The xaccTransCommitEdit() method indicates that the changes to the transaction and its splits are com...
void xaccTransBeginEdit(Transaction *trans)
The xaccTransBeginEdit() method must be called before any changes are made to a transaction or any of...
All type declarations for the whole Gnucash engine.
virtual bool commit_transaction() noexcept=0
Returns TRUE if successful, FALSE if error.
load and save data to SQL
Generic api to store and retrieve preferences.
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:2994
API for the transaction logger.
Business Invoice Interface.
QofIdType e_type
Entity type.
Definition: qofinstance.h:75
gboolean qof_book_is_readonly(const QofBook *book)
Return whether the book is read only.
Definition: qofbook.cpp:497
void begin(QofInstance *) override
An object is about to be edited.
Encapsulate the connection to the database.
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.
Data-passing struct for callbacks to qof_object_foreach() used in GncSqlObjectBackend::write().
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Definition: Account.cpp:1475
void connect(GncSqlConnection *conn) noexcept
Connect the backend to a GncSqlConnection.
cannot write to file/directory
Definition: qofbackend.h:68
load and save entry data to SQL
bool m_is_pristine_db
Are we saving to a new pristine db?
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
Pure virtual class to iterate over a query result set.
QofCollection * qof_book_get_collection(const QofBook *book, QofIdType entity_type)
Return The table of entities of the given type.
Definition: qofbook.cpp:521
virtual bool begin_transaction() noexcept=0
Returns TRUE if successful, false if error.
load and save data to SQL
virtual bool rollback_transaction() noexcept=0
Returns TRUE if successful, FALSE if error.
load and save invoice data to SQL
virtual bool does_table_exist(const std::string &) const noexcept=0
Returns true if successful.
A Query.
Definition: qofquery.cpp:74
bool reset_version_info() noexcept
Resets the version table information by removing all version table info.
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
Definition: Account.cpp:1516
uint_t get_table_version(const std::string &table_name) const noexcept
Returns the version number for a DB table.
SplitList * xaccTransGetSplitList(const Transaction *trans)
The xaccTransGetSplitList() method returns a GList of the splits in a transaction.
Billing Term interface.
Commodity handling public routines.
void set_error(QofBackendError err)
Set the error value only if there isn&#39;t already an error already.
Definition: qof-backend.cpp:56
void xaccLogEnable(void)
document me
Definition: TransLog.cpp:99
Main SQL backend structure.
void finalize_version_info() noexcept
Finalizes DB table version information.
load and save tax table data to SQL