Line data Source code
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 : \********************************************************************/
21 : /** @file gnc-account-sql.c
22 : * @brief load and save data to SQL
23 : * @author Copyright (c) 2006-2008 Phil Longstaff <plongstaff@rogers.com>
24 : *
25 : * This file implements the top-level QofBackend API for saving/
26 : * restoring data to/from an SQL db
27 : */
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 15 : GncSqlAccountBackend::GncSqlAccountBackend() :
101 30 : GncSqlObjectBackend(TABLE_VERSION, GNC_ID_ACCOUNT,
102 75 : TABLE_NAME, col_table) {}
103 :
104 : struct ParentGuid
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 40 : get_parent (gpointer pObject)
117 : {
118 : const Account* pAccount;
119 : const Account* pParent;
120 : const GncGUID* parent_guid;
121 :
122 40 : g_return_val_if_fail (pObject != NULL, NULL);
123 40 : g_return_val_if_fail (GNC_IS_ACCOUNT (pObject), NULL);
124 :
125 40 : pAccount = GNC_ACCOUNT (pObject);
126 40 : pParent = gnc_account_get_parent (pAccount);
127 40 : if (pParent == NULL)
128 : {
129 14 : parent_guid = NULL;
130 : }
131 : else
132 : {
133 26 : parent_guid = qof_instance_get_guid (QOF_INSTANCE (pParent));
134 : }
135 :
136 40 : return (gpointer)parent_guid;
137 : }
138 :
139 : static void
140 28 : set_parent (gpointer pObject, gpointer pValue)
141 : {
142 : Account* pAccount;
143 : QofBook* pBook;
144 28 : GncGUID* guid = (GncGUID*)pValue;
145 : Account* pParent;
146 :
147 28 : g_return_if_fail (pObject != NULL);
148 28 : g_return_if_fail (GNC_IS_ACCOUNT (pObject));
149 :
150 28 : pAccount = GNC_ACCOUNT (pObject);
151 28 : pBook = qof_instance_get_book (QOF_INSTANCE (pAccount));
152 28 : if (guid != NULL)
153 : {
154 28 : pParent = xaccAccountLookup (guid, pBook);
155 28 : if (pParent != NULL)
156 : {
157 28 : gnc_account_append_child (pParent, pAccount);
158 : }
159 : }
160 : }
161 :
162 : static void
163 0 : set_parent_guid (gpointer pObject, gpointer pValue)
164 : {
165 0 : g_return_if_fail (pObject != NULL);
166 0 : g_return_if_fail (pValue != NULL);
167 0 : ParentGuidPtr s = reinterpret_cast<decltype(s)>(pObject);
168 0 : s->guid = *static_cast<GncGUID*>(pValue);
169 : }
170 :
171 : static Account*
172 42 : load_single_account (GncSqlBackend* sql_be, GncSqlRow& row,
173 : ParentGuidVec& l_accounts_needing_parents)
174 : {
175 : const GncGUID* guid;
176 42 : Account* pAccount = NULL;
177 :
178 42 : g_return_val_if_fail (sql_be != NULL, NULL);
179 :
180 42 : guid = gnc_sql_load_guid (sql_be, row);
181 42 : if (guid != NULL)
182 : {
183 42 : pAccount = xaccAccountLookup (guid, sql_be->book());
184 : }
185 42 : if (pAccount == NULL)
186 : {
187 28 : pAccount = xaccMallocAccount (sql_be->book());
188 : }
189 42 : xaccAccountBeginEdit (pAccount);
190 42 : gnc_sql_load_object (sql_be, row, GNC_ID_ACCOUNT, pAccount, col_table);
191 42 : 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 42 : if (gnc_account_get_parent (pAccount) == NULL
197 42 : && pAccount != gnc_book_get_root_account (sql_be->book()))
198 : {
199 7 : auto s = new ParentGuid;
200 :
201 7 : s->pAccount = pAccount;
202 7 : gnc_sql_load_object (sql_be, row, GNC_ID_ACCOUNT, s, parent_col_table);
203 7 : l_accounts_needing_parents.push_back(s);
204 : }
205 :
206 42 : return pAccount;
207 : }
208 :
209 : void
210 7 : GncSqlAccountBackend::load_all (GncSqlBackend* sql_be)
211 : {
212 : QofBook* pBook;
213 7 : ParentGuidVec l_accounts_needing_parents;
214 7 : g_return_if_fail (sql_be != NULL);
215 :
216 7 : ENTER ("");
217 :
218 7 : pBook = sql_be->book();
219 :
220 7 : std::string sql("SELECT * FROM " TABLE_NAME);
221 7 : auto stmt = sql_be->create_statement_from_sql(sql);
222 7 : auto result = sql_be->execute_select_statement(stmt);
223 49 : for (auto row : *result)
224 49 : load_single_account (sql_be, row, l_accounts_needing_parents);
225 :
226 7 : sql = "SELECT DISTINCT guid FROM " TABLE_NAME;
227 7 : 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 7 : if (!l_accounts_needing_parents.empty())
236 : {
237 7 : auto progress_made = true;
238 7 : std::reverse(l_accounts_needing_parents.begin(),
239 : l_accounts_needing_parents.end());
240 7 : auto end = l_accounts_needing_parents.end();
241 14 : while (progress_made)
242 : {
243 7 : progress_made = false;
244 7 : end = std::remove_if(l_accounts_needing_parents.begin(), end,
245 7 : [&](ParentGuidPtr s)
246 : {
247 7 : auto pParent = xaccAccountLookup (&s->guid,
248 : sql_be->book());
249 7 : if (pParent != nullptr)
250 : {
251 0 : gnc_account_append_child (pParent,
252 : s->pAccount);
253 0 : progress_made = true;
254 0 : delete s;
255 0 : return true;
256 : }
257 7 : return false;
258 : });
259 : }
260 :
261 : /* Any non-ROOT accounts left over must be parented by the root account */
262 7 : auto root = gnc_book_get_root_account (pBook);
263 7 : end = std::remove_if(l_accounts_needing_parents.begin(), end,
264 7 : [&](ParentGuidPtr s)
265 : {
266 7 : if (xaccAccountGetType (s->pAccount) != ACCT_TYPE_ROOT)
267 0 : gnc_account_append_child (root, s->pAccount);
268 7 : delete s;
269 7 : return true;
270 : });
271 : }
272 :
273 7 : LEAVE ("");
274 7 : }
275 :
276 : /* ================================================================= */
277 : bool
278 40 : GncSqlAccountBackend::commit (GncSqlBackend* sql_be, QofInstance* inst)
279 : {
280 40 : Account* pAcc = GNC_ACCOUNT (inst);
281 : const GncGUID* guid;
282 : gboolean is_infant;
283 40 : gboolean is_ok = FALSE;
284 : gnc_commodity* commodity;
285 : E_DB_OPERATION op;
286 :
287 40 : g_return_val_if_fail (sql_be != NULL, FALSE);
288 40 : g_return_val_if_fail (inst != NULL, FALSE);
289 40 : g_return_val_if_fail (GNC_IS_ACCOUNT (inst), FALSE);
290 :
291 40 : ENTER ("inst=%p", inst);
292 :
293 40 : 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 40 : commodity = xaccAccountGetCommodity (pAcc);
300 :
301 40 : is_ok = TRUE;
302 40 : if (qof_instance_get_destroying (inst))
303 : {
304 0 : op = OP_DB_DELETE;
305 : }
306 40 : else if (sql_be->pristine() || is_infant)
307 : {
308 38 : op = OP_DB_INSERT;
309 : }
310 : else
311 : {
312 2 : op = OP_DB_UPDATE;
313 : }
314 :
315 : // If not deleting the account, ensure the commodity is in the db
316 40 : if (op != OP_DB_DELETE && commodity != NULL)
317 : {
318 27 : is_ok = sql_be->save_commodity(commodity);
319 : }
320 :
321 40 : if (is_ok)
322 : {
323 40 : is_ok = sql_be->do_db_operation (op, TABLE_NAME, GNC_ID_ACCOUNT, pAcc,
324 : col_table);
325 : }
326 :
327 40 : if (is_ok)
328 : {
329 : // Now, commit or delete any slots
330 40 : guid = qof_instance_get_guid (inst);
331 40 : if (!qof_instance_get_destroying (inst))
332 : {
333 40 : is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst);
334 : }
335 : else
336 : {
337 0 : is_ok = gnc_sql_slots_delete (sql_be, guid);
338 : }
339 : }
340 :
341 40 : LEAVE ("is_ok=%d", is_ok);
342 :
343 40 : return is_ok;
344 : }
345 :
346 : /* ================================================================= */
347 :
348 : template<> void
349 34 : GncSqlColumnTableEntryImpl<CT_ACCOUNTREF>::load (const GncSqlBackend* sql_be,
350 : GncSqlRow& row,
351 : QofIdTypeConst obj_name,
352 : gpointer pObject) const noexcept
353 : {
354 34 : load_from_guid_ref(row, obj_name, pObject,
355 30 : [sql_be](GncGUID* g){
356 30 : return xaccAccountLookup(g, sql_be->book());
357 : });
358 34 : }
359 :
360 : template<> void
361 54 : GncSqlColumnTableEntryImpl<CT_ACCOUNTREF>::add_to_table(ColVec& vec) const noexcept
362 : {
363 54 : add_objectref_guid_to_table(vec);
364 54 : }
365 :
366 : template<> void
367 33 : GncSqlColumnTableEntryImpl<CT_ACCOUNTREF>::add_to_query(QofIdTypeConst obj_name,
368 : const gpointer pObject,
369 : PairVec& vec)
370 : const noexcept
371 : {
372 33 : add_objectref_guid_to_query(obj_name, pObject, vec);
373 33 : }
374 :
375 : /* ========================== END OF FILE ===================== */
|