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 static void
275 scrub_txn_callback (QofInstance* inst, [[maybe_unused]] void* data)
276 {
277  auto trans = GNC_TRANSACTION(inst);
278  xaccTransBeginEdit(trans);
279  xaccTransCommitEdit(trans);
280 }
281 
282 void
283 GncSqlBackend::load (QofBook* book, QofBackendLoadType loadType)
284 {
285  Account* root;
286 
287  g_return_if_fail (book != NULL);
288 
289  ENTER ("sql_be=%p, book=%p", this, book);
290 
291  m_loading = TRUE;
292 
293  if (loadType == LOAD_TYPE_INITIAL_LOAD)
294  {
295  assert (m_book == nullptr);
296  m_book = book;
297 
298  auto num_types = m_backend_registry.size();
299  auto num_done = 0;
300 
301  /* Load any initial stuff. Some of this needs to happen in a certain order */
302  for (const auto& type : fixed_load_order)
303  {
304  num_done++;
305  auto obe = m_backend_registry.get_object_backend(type);
306  if (obe)
307  {
308  update_progress(num_done * 100 / num_types);
309  obe->load_all(this);
310  }
311  }
312  for (const auto& type : business_fixed_load_order)
313  {
314  num_done++;
315  auto obe = m_backend_registry.get_object_backend(type);
316  if (obe)
317  {
318  update_progress(num_done * 100 / num_types);
319  obe->load_all(this);
320  }
321  }
322 
323  root = gnc_book_get_root_account( book );
324  gnc_account_foreach_descendant(root, (AccountCb)xaccAccountBeginEdit,
325  nullptr);
326 
327  m_backend_registry.load_remaining(this);
328 
329  gnc_account_foreach_descendant(root, (AccountCb)xaccAccountCommitEdit,
330  nullptr);
331  }
332  else if (loadType == LOAD_TYPE_LOAD_ALL)
333  {
334  // Load all transactions
335  auto obe = m_backend_registry.get_object_backend (GNC_ID_TRANS);
336  obe->load_all (this);
337  }
338 
339  m_loading = FALSE;
340  std::for_each(m_postload_commodities.begin(), m_postload_commodities.end(),
341  [](gnc_commodity* comm) {
342  gnc_commodity_begin_edit(comm);
343  gnc_commodity_commit_edit(comm);
344  });
345  m_postload_commodities.clear();
346  /* We deferred the transaction scrub while loading because having
347  * m_loading true prevents changes from being written back to the
348  * database. Do that now.
349  */
350  auto transactions = qof_book_get_collection (book, GNC_ID_TRANS);
351  qof_collection_foreach(transactions, scrub_txn_callback, nullptr);
352 
353  /* Mark the session as clean -- though it should never be marked
354  * dirty with this backend
355  */
357  finish_progress();
358 
359  LEAVE ("");
360 }
361 
362 /* ================================================================= */
363 
364 bool
365 GncSqlBackend::write_account_tree(Account* root)
366 {
367  GList* descendants;
368  GList* node;
369  bool is_ok = true;
370 
371  g_return_val_if_fail (root != nullptr, false);
372 
373  auto obe = m_backend_registry.get_object_backend(GNC_ID_ACCOUNT);
374  is_ok = obe->commit (this, QOF_INSTANCE (root));
375  if (is_ok)
376  {
377  descendants = gnc_account_get_descendants (root);
378  for (node = descendants; node != NULL && is_ok; node = g_list_next (node))
379  {
380  is_ok = obe->commit(this, QOF_INSTANCE (GNC_ACCOUNT (node->data)));
381  if (!is_ok) break;
382  }
383  g_list_free (descendants);
384  }
385  update_progress(101.0);
386 
387  return is_ok;
388 }
389 
390 bool
391 GncSqlBackend::write_accounts()
392 {
393  update_progress(101.0);
394  auto is_ok = write_account_tree (gnc_book_get_root_account (m_book));
395  if (is_ok)
396  {
397  update_progress(101.0);
398  is_ok = write_account_tree (gnc_book_get_template_root(m_book));
399  }
400 
401  return is_ok;
402 }
403 
404 static gboolean // Can't be bool because of signature for xaccAccountTreeForEach
405 write_tx (Transaction* tx, gpointer data)
406 {
407  auto s = static_cast<write_objects_t*>(data);
408 
409  g_return_val_if_fail (tx != NULL, 0);
410  g_return_val_if_fail (data != NULL, 0);
411 
412  s->commit (QOF_INSTANCE (tx));
413  auto splitbe = s->be->get_object_backend(GNC_ID_SPLIT);
414  for (auto split_node = xaccTransGetSplitList (tx);
415  split_node != nullptr && s->is_ok;
416  split_node = g_list_next (split_node))
417  {
418  s->is_ok = splitbe->commit(s->be, QOF_INSTANCE(split_node->data));
419  }
420  s->be->update_progress (101.0);
421  return (s->is_ok ? 0 : 1);
422 }
423 
424 bool
425 GncSqlBackend::write_transactions()
426 {
427  auto obe = m_backend_registry.get_object_backend(GNC_ID_TRANS);
428  write_objects_t data{this, TRUE, obe.get()};
429 
431  gnc_book_get_root_account (m_book), write_tx, &data);
432  update_progress(101.0);
433  return data.is_ok;
434 }
435 
436 bool
437 GncSqlBackend::write_template_transactions()
438 {
439  auto obe = m_backend_registry.get_object_backend(GNC_ID_TRANS);
440  write_objects_t data{this, true, obe.get()};
442  if (gnc_account_n_descendants (ra) > 0)
443  {
444  (void)xaccAccountTreeForEachTransaction (ra, write_tx, &data);
445  update_progress(101.0);
446  }
447 
448  return data.is_ok;
449 }
450 
451 bool
452 GncSqlBackend::write_schedXactions()
453 {
454  GList* schedXactions;
455  SchedXaction* tmpSX;
456  bool is_ok = true;
457 
458  schedXactions = gnc_book_get_schedxactions (m_book)->sx_list;
459  auto obe = m_backend_registry.get_object_backend(GNC_ID_SCHEDXACTION);
460 
461  for (; schedXactions != NULL && is_ok; schedXactions = schedXactions->next)
462  {
463  tmpSX = static_cast<decltype (tmpSX)> (schedXactions->data);
464  is_ok = obe->commit (this, QOF_INSTANCE (tmpSX));
465  }
466  update_progress(101.0);
467 
468  return is_ok;
469 }
470 
471 void
472 GncSqlBackend::sync(QofBook* book)
473 {
474  g_return_if_fail (book != NULL);
475  g_return_if_fail (m_conn != nullptr);
476 
478  ENTER ("book=%p, sql_be->book=%p", book, m_book);
479  update_progress(101.0);
480 
481  /* Create new tables */
482  m_is_pristine_db = true;
483  create_tables();
484 
485  /* Save all contents */
486  m_book = book;
487  auto is_ok = m_conn->begin_transaction();
488 
489  // FIXME: should write the set of commodities that are used
490  // write_commodities(sql_be, book);
491  if (is_ok)
492  {
493  auto obe = m_backend_registry.get_object_backend(GNC_ID_BOOK);
494  is_ok = obe->commit (this, QOF_INSTANCE (book));
495  }
496  if (is_ok)
497  {
498  is_ok = write_accounts();
499  }
500  if (is_ok)
501  {
502  is_ok = write_transactions();
503  }
504  if (is_ok)
505  {
506  is_ok = write_template_transactions();
507  }
508  if (is_ok)
509  {
510  is_ok = write_schedXactions();
511  }
512  if (is_ok)
513  {
514  for (auto entry : m_backend_registry)
515  std::get<1>(entry)->write (this);
516  }
517  if (is_ok)
518  {
519  is_ok = m_conn->commit_transaction();
520  }
521  if (is_ok)
522  {
523  m_is_pristine_db = false;
524 
525  /* Mark the session as clean -- though it shouldn't ever get
526  * marked dirty with this backend
527  */
529  }
530  else
531  {
534  }
535  finish_progress();
536  LEAVE ("book=%p", book);
537 }
538 
539 /* ================================================================= */
540 /* Routines to deal with the creation of multiple books. */
541 
542 void
544 {
545  //g_return_if_fail (inst != NULL);
546 
547  //ENTER (" ");
548  //LEAVE ("");
549 }
550 
551 void
553 {
554  //g_return_if_fail (inst != NULL);
555 
556  //ENTER (" ");
557  //LEAVE ("");
558 }
559 
560 void
562 {
563  m_postload_commodities.push_back(commodity);
564 }
565 
566 GncSqlObjectBackendPtr
567 GncSqlBackend::get_object_backend(const std::string& type) const noexcept
568 {
569  return m_backend_registry.get_object_backend(type);
570 }
571 
572 
573 /* Commit_edit handler - find the correct backend handler for this object
574  * type and call its commit handler
575  */
576 void
578 {
579  gboolean is_dirty;
580  gboolean is_destroying;
581  gboolean is_infant;
582 
583  g_return_if_fail (inst != NULL);
584  g_return_if_fail (m_conn != nullptr);
585 
587  {
589  (void)m_conn->rollback_transaction ();
590  return;
591  }
592  /* During initial load where objects are being created, don't commit
593  anything, but do mark the object as clean. */
594  if (m_loading)
595  {
596  qof_instance_mark_clean (inst);
597  return;
598  }
599 
600  // The engine has a PriceDB object but it isn't in the database
601  if (strcmp (inst->e_type, "PriceDB") == 0)
602  {
603  qof_instance_mark_clean (inst);
605  return;
606  }
607 
608  ENTER (" ");
609 
610  is_dirty = qof_instance_get_dirty_flag (inst);
611  is_destroying = qof_instance_get_destroying (inst);
612  is_infant = qof_instance_get_infant (inst);
613 
614  DEBUG ("%s dirty = %d, do_free = %d, infant = %d\n",
615  (inst->e_type ? inst->e_type : "(null)"),
616  is_dirty, is_destroying, is_infant);
617 
618  if (!is_dirty && !is_destroying)
619  {
620  LEAVE ("!dirty OR !destroying");
621  return;
622  }
623 
624  if (!m_conn->begin_transaction ())
625  {
626  PERR ("begin_transaction failed\n");
627  LEAVE ("Rolled back - database transaction begin error");
628  return;
629  }
630 
631  bool is_ok = true;
632 
633  auto obe = m_backend_registry.get_object_backend(std::string{inst->e_type});
634  if (obe != nullptr)
635  is_ok = obe->commit(this, inst);
636  else
637  {
638  PERR ("Unknown object type '%s'\n", inst->e_type);
639  (void)m_conn->rollback_transaction ();
640 
641  // Don't let unknown items still mark the book as being dirty
643  qof_instance_mark_clean (inst);
644  LEAVE ("Rolled back - unknown object type");
645  return;
646  }
647  if (!is_ok)
648  {
649  // Error - roll it back
650  (void)m_conn->rollback_transaction();
651 
652  // This *should* leave things marked dirty
653  LEAVE ("Rolled back - database error");
654  return;
655  }
656 
657  (void)m_conn->commit_transaction ();
658 
660  qof_instance_mark_clean (inst);
661 
662  LEAVE ("");
663 }
664 
665 
672 void
674 {
675  g_return_if_fail (m_conn != nullptr);
676  if (m_conn->does_table_exist (VERSION_TABLE_NAME))
677  {
678  std::string sql {"SELECT * FROM "};
679  sql += VERSION_TABLE_NAME;
680  auto stmt = m_conn->create_statement_from_sql(sql);
681  auto result = m_conn->execute_select_statement (stmt);
682  for (const auto& row : *result)
683  {
684  auto name = row.get_string_at_col (TABLE_COL_NAME);
685  auto version = row.get_int_at_col (VERSION_COL_NAME);
686  if (name && version)
687  m_versions.push_back(std::make_pair(*name, static_cast<unsigned int>(*version)));
688  }
689  }
690  else
691  {
692  create_table (VERSION_TABLE_NAME, version_table);
693  set_table_version("Gnucash", gnc_prefs_get_long_version ());
694  set_table_version("Gnucash-Resave", GNUCASH_RESAVE_VERSION);
695  }
696 }
697 
705 bool
707 {
708  bool ok = create_table (VERSION_TABLE_NAME, version_table);
709  m_versions.clear();
710  set_table_version ("Gnucash", gnc_prefs_get_long_version ());
711  set_table_version ("Gnucash-Resave", GNUCASH_RESAVE_VERSION);
712  return ok;
713 }
714 
720 void
722 {
723  m_versions.clear();
724 }
725 
726 unsigned int
727 GncSqlBackend::get_table_version(const std::string& table_name) const noexcept
728 {
729  /* If the db is pristine because it's being saved, the table does not exist. */
730  if (m_is_pristine_db)
731  return 0;
732 
733  auto version = std::find_if(m_versions.begin(), m_versions.end(),
734  [table_name](const VersionPair& version) {
735  return version.first == table_name; });
736  if (version != m_versions.end())
737  return version->second;
738  return 0;
739 }
740 
750 bool
751 GncSqlBackend::set_table_version (const std::string& table_name,
752  uint_t version) noexcept
753 {
754  g_return_val_if_fail (version > 0, false);
755 
756  unsigned int cur_version{0};
757  std::stringstream sql;
758  auto ver_entry = std::find_if(m_versions.begin(), m_versions.end(),
759  [table_name](const VersionPair& ver) {
760  return ver.first == table_name; });
761  if (ver_entry != m_versions.end())
762  cur_version = ver_entry->second;
763  if (cur_version != version)
764  {
765  if (cur_version == 0)
766  {
767  sql << "INSERT INTO " << VERSION_TABLE_NAME << " VALUES('" <<
768  table_name << "'," << version <<")";
769  m_versions.push_back(std::make_pair(table_name, version));
770  }
771  else
772  {
773  sql << "UPDATE " << VERSION_TABLE_NAME << " SET " <<
774  VERSION_COL_NAME << "=" << version << " WHERE " <<
775  TABLE_COL_NAME << "='" << table_name << "'";
776  ver_entry->second = version;
777  }
778  auto stmt = create_statement_from_sql(sql.str());
779  auto status = execute_nonselect_statement (stmt);
780  if (status == -1)
781  {
782  PERR ("SQL error: %s\n", sql.str().c_str());
784  return false;
785  }
786  }
787 
788  return true;
789 }
790 
791 void
792 GncSqlBackend::upgrade_table (const std::string& table_name,
793  const EntryVec& col_table) noexcept
794 {
795  DEBUG ("Upgrading %s table\n", table_name.c_str());
796 
797  auto temp_table_name = table_name + "_new";
798  create_table (temp_table_name, col_table);
799  std::stringstream sql;
800  sql << "INSERT INTO " << temp_table_name << " SELECT * FROM " << table_name;
801  auto stmt = create_statement_from_sql(sql.str());
802  execute_nonselect_statement(stmt);
803 
804  sql.str("");
805  sql << "DROP TABLE " << table_name;
806  stmt = create_statement_from_sql(sql.str());
807  execute_nonselect_statement(stmt);
808 
809  sql.str("");
810  sql << "ALTER TABLE " << temp_table_name << " RENAME TO " << table_name;
811  stmt = create_statement_from_sql(sql.str());
812  execute_nonselect_statement(stmt);
813 }
814 
815 static inline PairVec
816 get_object_values (QofIdTypeConst obj_name,
817  gpointer pObject, const EntryVec& table)
818 {
819  PairVec vec;
820 
821  for (auto const& table_row : table)
822  {
823  if (!(table_row->is_autoincr()))
824  {
825  table_row->add_to_query (obj_name, pObject, vec);
826  }
827  }
828  return vec;
829 }
830 
831 bool
832 GncSqlBackend::object_in_db (const char* table_name, QofIdTypeConst obj_name,
833  const gpointer pObject, const EntryVec& table) const noexcept
834 {
835  g_return_val_if_fail (table_name != nullptr, false);
836  g_return_val_if_fail (obj_name != nullptr, false);
837  g_return_val_if_fail (pObject != nullptr, false);
838 
839  /* SELECT * FROM */
840  auto sql = std::string{"SELECT "} + table[0]->name() + " FROM " + table_name;
841  auto stmt = create_statement_from_sql(sql.c_str());
842  assert (stmt != nullptr);
843 
844  /* WHERE */
845  PairVec values{get_object_values(obj_name, pObject, table)};
846  /* We want only the first item in the table, which should be the PK. */
847  values.resize(1);
848  stmt->add_where_cond(obj_name, values);
849  auto result = execute_select_statement (stmt);
850  return (result != nullptr && result->size() > 0);
851 }
852 
853 bool
854 GncSqlBackend::do_db_operation (E_DB_OPERATION op, const char* table_name,
855  QofIdTypeConst obj_name, gpointer pObject,
856  const EntryVec& table) const noexcept
857 {
858  GncSqlStatementPtr stmt;
859 
860  g_return_val_if_fail (table_name != nullptr, false);
861  g_return_val_if_fail (obj_name != nullptr, false);
862  g_return_val_if_fail (pObject != nullptr, false);
863 
864  switch(op)
865  {
866  case OP_DB_INSERT:
867  stmt = build_insert_statement (table_name, obj_name, pObject, table);
868  break;
869  case OP_DB_UPDATE:
870  stmt = build_update_statement (table_name, obj_name, pObject, table);
871  break;
872  case OP_DB_DELETE:
873  stmt = build_delete_statement (table_name, obj_name, pObject, table);
874  break;
875  }
876  if (stmt == nullptr)
877  return false;
878  return (execute_nonselect_statement(stmt) != -1);
879 }
880 
881 bool
882 GncSqlBackend::save_commodity(gnc_commodity* comm) noexcept
883 {
884  if (comm == nullptr) return false;
885  QofInstance* inst = QOF_INSTANCE(comm);
886  auto obe = m_backend_registry.get_object_backend(std::string(inst->e_type));
887  if (obe && !obe->instance_in_db(this, inst))
888  return obe->commit(this, inst);
889  return true;
890 }
891 
892 GncSqlStatementPtr
893 GncSqlBackend::build_insert_statement (const char* table_name,
894  QofIdTypeConst obj_name,
895  gpointer pObject,
896  const EntryVec& table) const noexcept
897 {
898  GncSqlStatementPtr stmt;
899  PairVec col_values;
900  std::ostringstream sql;
901 
902  g_return_val_if_fail (table_name != nullptr, nullptr);
903  g_return_val_if_fail (obj_name != nullptr, nullptr);
904  g_return_val_if_fail (pObject != nullptr, nullptr);
905  PairVec values{get_object_values(obj_name, pObject, table)};
906 
907  sql << "INSERT INTO " << table_name <<"(";
908  for (auto const& col_value : values)
909  {
910  if (col_value != *values.begin())
911  sql << ",";
912  sql << col_value.first;
913  }
914 
915  sql << ") VALUES(";
916  for (const auto& col_value : values)
917  {
918  if (col_value != *values.begin())
919  sql << ",";
920  sql << col_value.second;
921  }
922  sql << ")";
923 
924  stmt = create_statement_from_sql(sql.str());
925  return stmt;
926 }
927 
928 GncSqlStatementPtr
929 GncSqlBackend::build_update_statement(const gchar* table_name,
930  QofIdTypeConst obj_name, gpointer pObject,
931  const EntryVec& table) const noexcept
932 {
933  GncSqlStatementPtr stmt;
934  std::ostringstream sql;
935 
936  g_return_val_if_fail (table_name != nullptr, nullptr);
937  g_return_val_if_fail (obj_name != nullptr, nullptr);
938  g_return_val_if_fail (pObject != nullptr, nullptr);
939 
940 
941  PairVec values{get_object_values (obj_name, pObject, table)};
942 
943  // Create the SQL statement
944  sql << "UPDATE " << table_name << " SET ";
945 
946  for (auto const& col_value : values)
947  {
948  if (col_value != *values.begin())
949  sql << ",";
950  sql << col_value.first << "=" <<
951  col_value.second;
952  }
953 
954  stmt = create_statement_from_sql(sql.str());
955  /* We want our where condition to be just the first column and
956  * value, i.e. the guid of the object.
957  */
958  values.erase(values.begin() + 1, values.end());
959  stmt->add_where_cond(obj_name, values);
960  return stmt;
961 }
962 
963 GncSqlStatementPtr
964 GncSqlBackend::build_delete_statement(const gchar* table_name,
965  QofIdTypeConst obj_name,
966  gpointer pObject,
967  const EntryVec& table) const noexcept
968 {
969  std::ostringstream sql;
970 
971  g_return_val_if_fail (table_name != nullptr, nullptr);
972  g_return_val_if_fail (obj_name != nullptr, nullptr);
973  g_return_val_if_fail (pObject != nullptr, nullptr);
974 
975  sql << "DELETE FROM " << table_name;
976  auto stmt = create_statement_from_sql (sql.str());
977 
978  /* WHERE */
979  PairVec values;
980  table[0]->add_to_query (obj_name, pObject, values);
981  PairVec col_values{values[0]};
982  stmt->add_where_cond (obj_name, col_values);
983 
984  return stmt;
985 }
986 
987 GncSqlBackend::ObjectBackendRegistry::ObjectBackendRegistry()
988 {
989  register_backend(std::make_shared<GncSqlBookBackend>());
990  register_backend(std::make_shared<GncSqlCommodityBackend>());
991  register_backend(std::make_shared<GncSqlAccountBackend>());
992  register_backend(std::make_shared<GncSqlBudgetBackend>());
993  register_backend(std::make_shared<GncSqlPriceBackend>());
994  register_backend(std::make_shared<GncSqlTransBackend>());
995  register_backend(std::make_shared<GncSqlSplitBackend>());
996  register_backend(std::make_shared<GncSqlSlotsBackend>());
997  register_backend(std::make_shared<GncSqlRecurrenceBackend>());
998  register_backend(std::make_shared<GncSqlSchedXactionBackend>());
999  register_backend(std::make_shared<GncSqlLotsBackend>());
1000  register_backend(std::make_shared<GncSqlBillTermBackend>());
1001  register_backend(std::make_shared<GncSqlCustomerBackend>());
1002  register_backend(std::make_shared<GncSqlEmployeeBackend>());
1003  register_backend(std::make_shared<GncSqlEntryBackend>());
1004  register_backend(std::make_shared<GncSqlInvoiceBackend>());
1005  register_backend(std::make_shared<GncSqlJobBackend>());
1006  register_backend(std::make_shared<GncSqlOrderBackend>());
1007  register_backend(std::make_shared<GncSqlTaxTableBackend>());
1008  register_backend(std::make_shared<GncSqlVendorBackend>());
1009 }
1010 
1011 void
1012 GncSqlBackend::ObjectBackendRegistry::register_backend(OBEEntry&& entry) noexcept
1013 {
1014  m_registry.emplace_back(entry);
1015 }
1016 
1017 void
1018 GncSqlBackend::ObjectBackendRegistry::register_backend(GncSqlObjectBackendPtr obe) noexcept
1019 {
1020  m_registry.emplace_back(make_tuple(std::string{obe->type()}, obe));
1021 }
1022 
1023 GncSqlObjectBackendPtr
1024 GncSqlBackend::ObjectBackendRegistry::get_object_backend(const std::string& type) const
1025 {
1026  auto entry = std::find_if(m_registry.begin(), m_registry.end(),
1027  [type](const OBEEntry& entry){
1028  return type == std::get<0>(entry);
1029  });
1030  if (entry == m_registry.end())
1031  return nullptr;
1032 
1033  return std::get<1>(*entry);
1034 }
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.
#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
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
Main SQL backend structure.
void finalize_version_info() noexcept
Finalizes DB table version information.
load and save tax table data to SQL