GnuCash  5.6-150-g038405b370+
gnc-account-sql.cpp
1 /********************************************************************
2  * gnc-account-sql.c: load and save data to SQL *
3  * *
4  * This program is free software; you can redistribute it and/or *
5  * modify it under the terms of the GNU General Public License as *
6  * published by the Free Software Foundation; either version 2 of *
7  * the License, or (at your option) any later version. *
8  * *
9  * This program is distributed in the hope that it will be useful, *
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12  * GNU General Public License for more details. *
13  * *
14  * You should have received a copy of the GNU General Public License*
15  * along with this program; if not, contact: *
16  * *
17  * Free Software Foundation Voice: +1-617-542-5942 *
18  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
19  * Boston, MA 02110-1301, USA gnu@gnu.org *
20 \********************************************************************/
28 #include <guid.hpp>
29 #include <config.h>
30 
31 #include <glib.h>
32 
33 #include "qof.h"
34 #include "Account.h"
35 #include "AccountP.hpp"
36 #include "gnc-commodity.h"
37 
38 #if defined( S_SPLINT_S )
39 #include "splint-defs.h"
40 #endif
41 
42 #include <string>
43 #include <vector>
44 #include <algorithm>
45 
46 #include "gnc-sql-connection.hpp"
47 #include "gnc-sql-backend.hpp"
48 #include "gnc-sql-object-backend.hpp"
49 #include "gnc-sql-column-table-entry.hpp"
50 #include "gnc-account-sql.h"
51 #include "gnc-commodity-sql.h"
52 #include "gnc-slots-sql.h"
53 #include "gnc-transaction-sql.h"
54 
55 static QofLogModule log_module = G_LOG_DOMAIN;
56 
57 #define TABLE_NAME "accounts"
58 #define TABLE_VERSION 1
59 
60 static gpointer get_parent (gpointer pObject);
61 static void set_parent (gpointer pObject, gpointer pValue);
62 static void set_parent_guid (gpointer pObject, gpointer pValue);
63 
64 #define ACCOUNT_MAX_NAME_LEN 2048
65 #define ACCOUNT_MAX_TYPE_LEN 2048
66 #define ACCOUNT_MAX_CODE_LEN 2048
67 #define ACCOUNT_MAX_DESCRIPTION_LEN 2048
68 
69 using AccountVec = std::vector<Account*>;
70 
71 static const EntryVec col_table
72 {
73  gnc_sql_make_table_entry<CT_GUID>("guid", 0, COL_NNUL | COL_PKEY, "guid" ),
74  gnc_sql_make_table_entry<CT_STRING>(
75  "name", ACCOUNT_MAX_NAME_LEN, COL_NNUL, "name"),
76  gnc_sql_make_table_entry<CT_STRING>("account_type", ACCOUNT_MAX_TYPE_LEN,
77  COL_NNUL, ACCOUNT_TYPE_, true),
78  gnc_sql_make_table_entry<CT_COMMODITYREF>(
79  "commodity_guid", 0, 0, "commodity"),
80  gnc_sql_make_table_entry<CT_INT>(
81  "commodity_scu", 0, COL_NNUL, "commodity-scu"),
82  gnc_sql_make_table_entry<CT_BOOLEAN>(
83  "non_std_scu", 0, COL_NNUL, "non-std-scu"),
84  gnc_sql_make_table_entry<CT_GUID>("parent_guid", 0, 0,
85  (QofAccessFunc)get_parent,
86  (QofSetterFunc)set_parent),
87  gnc_sql_make_table_entry<CT_STRING>(
88  "code", ACCOUNT_MAX_CODE_LEN, 0, "code"),
89  gnc_sql_make_table_entry<CT_STRING>(
90  "description", ACCOUNT_MAX_DESCRIPTION_LEN, 0, "description"),
91  gnc_sql_make_table_entry<CT_BOOLEAN>("hidden", 0, 0, "hidden"),
92  gnc_sql_make_table_entry<CT_BOOLEAN>("placeholder", 0, 0, "placeholder"),
93 };
94 static EntryVec parent_col_table
95 ({
96  gnc_sql_make_table_entry<CT_GUID>(
97  "parent_guid", 0, 0, nullptr, (QofSetterFunc)set_parent_guid),
98 });
99 
100 GncSqlAccountBackend::GncSqlAccountBackend() :
101  GncSqlObjectBackend(TABLE_VERSION, GNC_ID_ACCOUNT,
102  TABLE_NAME, col_table) {}
103 
105 {
106  Account* pAccount;
107  GncGUID guid;
108 };
109 
110 using ParentGuidPtr = ParentGuid*; // Can't pass std::shared_ptr<ParentGuid> as a gpointer.
111 using ParentGuidVec = std::vector<ParentGuidPtr>;
112 
113 /* ================================================================= */
114 
115 static gpointer
116 get_parent (gpointer pObject)
117 {
118  const Account* pAccount;
119  const Account* pParent;
120  const GncGUID* parent_guid;
121 
122  g_return_val_if_fail (pObject != NULL, NULL);
123  g_return_val_if_fail (GNC_IS_ACCOUNT (pObject), NULL);
124 
125  pAccount = GNC_ACCOUNT (pObject);
126  pParent = gnc_account_get_parent (pAccount);
127  if (pParent == NULL)
128  {
129  parent_guid = NULL;
130  }
131  else
132  {
133  parent_guid = qof_instance_get_guid (QOF_INSTANCE (pParent));
134  }
135 
136  return (gpointer)parent_guid;
137 }
138 
139 static void
140 set_parent (gpointer pObject, gpointer pValue)
141 {
142  Account* pAccount;
143  QofBook* pBook;
144  GncGUID* guid = (GncGUID*)pValue;
145  Account* pParent;
146 
147  g_return_if_fail (pObject != NULL);
148  g_return_if_fail (GNC_IS_ACCOUNT (pObject));
149 
150  pAccount = GNC_ACCOUNT (pObject);
151  pBook = qof_instance_get_book (QOF_INSTANCE (pAccount));
152  if (guid != NULL)
153  {
154  pParent = xaccAccountLookup (guid, pBook);
155  if (pParent != NULL)
156  {
157  gnc_account_append_child (pParent, pAccount);
158  }
159  }
160 }
161 
162 static void
163 set_parent_guid (gpointer pObject, gpointer pValue)
164 {
165  g_return_if_fail (pObject != NULL);
166  g_return_if_fail (pValue != NULL);
167  ParentGuidPtr s = reinterpret_cast<decltype(s)>(pObject);
168  s->guid = *static_cast<GncGUID*>(pValue);
169 }
170 
171 static Account*
172 load_single_account (GncSqlBackend* sql_be, GncSqlRow& row,
173  ParentGuidVec& l_accounts_needing_parents)
174 {
175  const GncGUID* guid;
176  Account* pAccount = NULL;
177 
178  g_return_val_if_fail (sql_be != NULL, NULL);
179 
180  guid = gnc_sql_load_guid (sql_be, row);
181  if (guid != NULL)
182  {
183  pAccount = xaccAccountLookup (guid, sql_be->book());
184  }
185  if (pAccount == NULL)
186  {
187  pAccount = xaccMallocAccount (sql_be->book());
188  }
189  xaccAccountBeginEdit (pAccount);
190  gnc_sql_load_object (sql_be, row, GNC_ID_ACCOUNT, pAccount, col_table);
191  xaccAccountCommitEdit (pAccount);
192 
193  /* If we don't have a parent and this isn't the root account, it might be
194  because the parent account hasn't been loaded yet. Remember the account
195  and its parent guid for later. */
196  if (gnc_account_get_parent (pAccount) == NULL
197  && pAccount != gnc_book_get_root_account (sql_be->book()))
198  {
199  auto s = new ParentGuid;
200 
201  s->pAccount = pAccount;
202  gnc_sql_load_object (sql_be, row, GNC_ID_ACCOUNT, s, parent_col_table);
203  l_accounts_needing_parents.push_back(s);
204  }
205 
206  return pAccount;
207 }
208 
209 void
211 {
212  QofBook* pBook;
213  ParentGuidVec l_accounts_needing_parents;
214  g_return_if_fail (sql_be != NULL);
215 
216  ENTER ("");
217 
218  pBook = sql_be->book();
219 
220  std::string sql("SELECT * FROM " TABLE_NAME);
221  auto stmt = sql_be->create_statement_from_sql(sql);
222  auto result = sql_be->execute_select_statement(stmt);
223  for (auto row : *result)
224  load_single_account (sql_be, row, l_accounts_needing_parents);
225 
226  sql = "SELECT DISTINCT guid FROM " TABLE_NAME;
228  (BookLookupFn)xaccAccountLookup);
229 
230  /* While there are items on the list of accounts needing parents,
231  try to see if the parent has now been loaded. Theory says that if
232  items are removed from the front and added to the back if the
233  parent is still not available, then eventually, the list will
234  shrink to size 0. */
235  if (!l_accounts_needing_parents.empty())
236  {
237  auto progress_made = true;
238  std::reverse(l_accounts_needing_parents.begin(),
239  l_accounts_needing_parents.end());
240  auto end = l_accounts_needing_parents.end();
241  while (progress_made)
242  {
243  progress_made = false;
244  end = std::remove_if(l_accounts_needing_parents.begin(), end,
245  [&](ParentGuidPtr s)
246  {
247  auto pParent = xaccAccountLookup (&s->guid,
248  sql_be->book());
249  if (pParent != nullptr)
250  {
251  gnc_account_append_child (pParent,
252  s->pAccount);
253  progress_made = true;
254  delete s;
255  return true;
256  }
257  return false;
258  });
259  }
260 
261  /* Any non-ROOT accounts left over must be parented by the root account */
262  auto root = gnc_book_get_root_account (pBook);
263  end = std::remove_if(l_accounts_needing_parents.begin(), end,
264  [&](ParentGuidPtr s)
265  {
266  if (xaccAccountGetType (s->pAccount) != ACCT_TYPE_ROOT)
267  gnc_account_append_child (root, s->pAccount);
268  delete s;
269  return true;
270  });
271  }
272 
273  LEAVE ("");
274 }
275 
276 /* ================================================================= */
277 bool
279 {
280  Account* pAcc = GNC_ACCOUNT (inst);
281  const GncGUID* guid;
282  gboolean is_infant;
283  gboolean is_ok = FALSE;
284  gnc_commodity* commodity;
285  E_DB_OPERATION op;
286 
287  g_return_val_if_fail (sql_be != NULL, FALSE);
288  g_return_val_if_fail (inst != NULL, FALSE);
289  g_return_val_if_fail (GNC_IS_ACCOUNT (inst), FALSE);
290 
291  ENTER ("inst=%p", inst);
292 
293  is_infant = qof_instance_get_infant (inst);
294 
295  // If there is no commodity yet, this might be because a new account name
296  // has been entered directly into the register and an account window will
297  // be opened. The account info is not complete yet, but the name has been
298  // set, triggering this commit
299  commodity = xaccAccountGetCommodity (pAcc);
300 
301  is_ok = TRUE;
302  if (qof_instance_get_destroying (inst))
303  {
304  op = OP_DB_DELETE;
305  }
306  else if (sql_be->pristine() || is_infant)
307  {
308  op = OP_DB_INSERT;
309  }
310  else
311  {
312  op = OP_DB_UPDATE;
313  }
314 
315  // If not deleting the account, ensure the commodity is in the db
316  if (op != OP_DB_DELETE && commodity != NULL)
317  {
318  is_ok = sql_be->save_commodity(commodity);
319  }
320 
321  if (is_ok)
322  {
323  is_ok = sql_be->do_db_operation (op, TABLE_NAME, GNC_ID_ACCOUNT, pAcc,
324  col_table);
325  }
326 
327  if (is_ok)
328  {
329  // Now, commit or delete any slots
330  guid = qof_instance_get_guid (inst);
331  if (!qof_instance_get_destroying (inst))
332  {
333  is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst);
334  }
335  else
336  {
337  is_ok = gnc_sql_slots_delete (sql_be, guid);
338  }
339  }
340 
341  LEAVE ("is_ok=%d", is_ok);
342 
343  return is_ok;
344 }
345 
346 /* ================================================================= */
347 
348 template<> void
350  GncSqlRow& row,
351  QofIdTypeConst obj_name,
352  gpointer pObject) const noexcept
353 {
354  load_from_guid_ref(row, obj_name, pObject,
355  [sql_be](GncGUID* g){
356  return xaccAccountLookup(g, sql_be->book());
357  });
358 }
359 
360 template<> void
362 {
363  add_objectref_guid_to_table(vec);
364 }
365 
366 template<> void
368  const gpointer pObject,
369  PairVec& vec)
370  const noexcept
371 {
372  add_objectref_guid_to_query(obj_name, pObject, vec);
373 }
374 
375 /* ========================== END OF FILE ===================== */
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.
Account * gnc_account_get_parent(const Account *acc)
This routine returns a pointer to the parent of the specified account.
Definition: Account.cpp:2886
This is the private header for the account structure.
GncSqlResultPtr execute_select_statement(const GncSqlStatementPtr &stmt) const noexcept
Executes an SQL SELECT statement and returns the result rows.
void gnc_account_append_child(Account *new_parent, Account *child)
This function will remove from the child account any pre-existing parent relationship, and will then add the account as a child of the new parent.
Definition: Account.cpp:2787
const GncGUID * qof_instance_get_guid(gconstpointer inst)
Return the GncGUID of this instance.
void gnc_sql_slots_load_for_sql_subquery(GncSqlBackend *sql_be, const std::string subquery, BookLookupFn lookup_fn)
gnc_sql_slots_load_for_sql_subquery - Loads slots for all objects whose guid is supplied by a subquer...
QofBook * qof_instance_get_book(gconstpointer inst)
Return the book pointer.
#define G_LOG_DOMAIN
Functions providing the SX List as a plugin page.
load and save data to SQL
GNCAccountType xaccAccountGetType(const Account *acc)
Returns the account&#39;s account type.
Definition: Account.cpp:3217
const gchar * QofIdTypeConst
QofIdTypeConst declaration.
Definition: qofid.h:82
load and save accounts data to SQL
STRUCTS.
gboolean qof_instance_get_destroying(gconstpointer ptr)
Retrieve the flag that indicates whether or not this object is about to be destroyed.
gboolean gnc_sql_slots_save(GncSqlBackend *sql_be, const GncGUID *guid, gboolean is_infant, QofInstance *inst)
gnc_sql_slots_save - Saves slots for an object to the db.
void add_to_query(QofIdTypeConst obj_name, void *pObject, PairVec &vec) const noexcept override
Add a pair of the table column heading and object&#39;s value&#39;s string representation to a PairVec; used ...
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
void(* QofSetterFunc)(gpointer, gpointer)
The QofSetterFunc defines an function pointer for parameter setters.
Definition: qofclass.h:185
void load_all(GncSqlBackend *) override
Load all objects of m_type in the database into memory.
void load(const GncSqlBackend *sql_be, GncSqlRow &row, QofIdTypeConst obj_name, void *pObject) const noexcept override
Load a value into an object from the database row.
Account handling public routines.
bool save_commodity(gnc_commodity *comm) noexcept
Ensure that a commodity referenced in another object is in fact saved in the database.
Row of SQL Query results.
load and save accounts data to SQL
load and save data to SQL
gpointer(* QofAccessFunc)(gpointer object, const QofParam *param)
The QofAccessFunc defines an arbitrary function pointer for access functions.
Definition: qofclass.h:178
Encapsulates per-class table schema with functions to load, create a table, commit a changed front-en...
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Definition: Account.cpp:1475
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account&#39;s commodity.
Definition: Account.cpp:3359
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
gboolean gnc_sql_slots_delete(GncSqlBackend *sql_be, const GncGUID *guid)
gnc_sql_slots_delete - Deletes slots for an object from the db.
Account * xaccMallocAccount(QofBook *book)
Constructor.
Definition: Account.cpp:1271
void add_to_table(ColVec &vec) const noexcept override
Add a GncSqlColumnInfo structure for the column type to a ColVec.
The type used to store guids in C.
Definition: guid.h:75
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
Definition: Account.cpp:1516
bool commit(GncSqlBackend *, QofInstance *) override
UPDATE/INSERT a single instance of m_type_name into the database.
The hidden root account of an account tree.
Definition: Account.h:153
Commodity handling public routines.
Main SQL backend structure.
Account * xaccAccountLookup(const GncGUID *guid, QofBook *book)
The xaccAccountLookup() subroutine will return the account associated with the given id...
Definition: Account.cpp:2032