GnuCash c935c2f+
Loading...
Searching...
No Matches
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
55static QofLogModule log_module = G_LOG_DOMAIN;
56
57#define TABLE_NAME "accounts"
58#define TABLE_VERSION 1
59
60static gpointer get_parent (gpointer pObject);
61static void set_parent (gpointer pObject, gpointer pValue);
62static 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
69using AccountVec = std::vector<Account*>;
70
71static 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};
94static 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
100GncSqlAccountBackend::GncSqlAccountBackend() :
101 GncSqlObjectBackend(TABLE_VERSION, GNC_ID_ACCOUNT,
102 TABLE_NAME, col_table) {}
103
105{
106 Account* pAccount;
107 GncGUID guid;
108};
109
110using ParentGuidPtr = ParentGuid*; // Can't pass std::shared_ptr<ParentGuid> as a gpointer.
111using ParentGuidVec = std::vector<ParentGuidPtr>;
112
113/* ================================================================= */
114
115static gpointer
116get_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
139static void
140set_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
162static void
163set_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
171static Account*
172load_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
209void
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;
227 gnc_sql_slots_load_for_sql_subquery (sql_be, sql,
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/* ================================================================= */
277bool
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;
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
348template<> 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
360template<> void
362{
363 add_objectref_guid_to_table(vec);
364}
365
366template<> 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 ===================== */
This is the private header for the account structure.
Account handling public routines.
bool commit(GncSqlBackend *, QofInstance *) override
UPDATE/INSERT a single instance of m_type_name into the database.
void load_all(GncSqlBackend *) override
Load all objects of m_type in the database into memory.
Main SQL backend structure.
GncSqlResultPtr execute_select_statement(const GncSqlStatementPtr &stmt) const noexcept
Executes an SQL SELECT statement and returns the result rows.
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 save_commodity(gnc_commodity *comm) noexcept
Ensure that a commodity referenced in another object is in fact saved in the database.
void add_to_query(QofIdTypeConst obj_name, void *pObject, PairVec &vec) const noexcept override
Add a pair of the table column heading and object's value's string representation to a PairVec; used ...
void add_to_table(ColVec &vec) const noexcept override
Add a GncSqlColumnInfo structure for the column type to a ColVec.
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.
Encapsulates per-class table schema with functions to load, create a table, commit a changed front-en...
Row of SQL Query results.
load and save accounts data to SQL
load and save data to SQL
Commodity handling public routines.
load and save accounts data to SQL
load and save data to SQL
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
Definition Account.cpp:1516
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Definition Account.cpp:1475
void gnc_account_append_child(Account *new_parent, Account *child)
This function will remove from the child account any pre-existing parent relationship,...
Definition Account.cpp:2836
Account * gnc_account_get_parent(const Account *acc)
This routine returns a pointer to the parent of the specified account.
Definition Account.cpp:2935
Account * xaccMallocAccount(QofBook *book)
Constructor.
Definition Account.cpp:1270
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account's commodity
Definition Account.cpp:3408
Account * xaccAccountLookup(const GncGUID *guid, QofBook *book)
The xaccAccountLookup() subroutine will return the account associated with the given id,...
Definition Account.cpp:2050
gpointer(* QofAccessFunc)(gpointer object, const QofParam *param)
The QofAccessFunc defines an arbitrary function pointer for access functions.
Definition qofclass.h:123
void(* QofSetterFunc)(gpointer, gpointer)
The QofSetterFunc defines an function pointer for parameter setters.
Definition qofclass.h:130
const gchar * QofIdTypeConst
QofIdTypeConst declaration.
Definition qofid.h:82
QofBook * qof_instance_get_book(gconstpointer inst)
Return the book pointer.
const GncGUID * qof_instance_get_guid(gconstpointer inst)
Return the GncGUID of this instance.
gboolean qof_instance_get_destroying(gconstpointer ptr)
Retrieve the flag that indicates whether or not this object is about to be destroyed.
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#define ENTER(format, args...)
Print a function entry debugging message.
Definition qoflog.h:272
STRUCTS.
The type used to store guids in C.
Definition guid.h:75
QofBook reference.
Definition qofbook-p.hpp:47