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