1 /********************************************************************
2 * gnc-budget-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-budget-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 extern "C"
30 {
31 #include <config.h>
32
33 #include <glib.h>
34
35 #include "qof.h"
36 #include "Recurrence.h"
37 #include "gnc-budget.h"
38
39 #if defined( S_SPLINT_S )
40 #include "splint-defs.h"
41 #endif
42 }
43
44 #include "gnc-sql-connection.hpp"
45 #include "gnc-sql-backend.hpp"
46 #include "gnc-sql-object-backend.hpp"
47 #include "gnc-sql-column-table-entry.hpp"
48 #include "gnc-budget-sql.h"
49 #include "gnc-slots-sql.h"
50 #include "gnc-recurrence-sql.h"
51
52 #define BUDGET_TABLE "budgets"
53 #define TABLE_VERSION 1
54 #define AMOUNTS_TABLE "budget_amounts"
55 #define AMOUNTS_TABLE_VERSION 1
56
57 static QofLogModule log_module = G_LOG_DOMAIN;
58
59 #define BUDGET_MAX_NAME_LEN 2048
60 #define BUDGET_MAX_DESCRIPTION_LEN 2048
61
62 static const EntryVec col_table
63 {
64 gnc_sql_make_table_entry<CT_GUID>(
65 "guid", 0, COL_NNUL | COL_PKEY, "guid"),
66 gnc_sql_make_table_entry<CT_STRING>(
67 "name", BUDGET_MAX_NAME_LEN, COL_NNUL, "name"),
68 gnc_sql_make_table_entry<CT_STRING>(
69 "description", BUDGET_MAX_DESCRIPTION_LEN, 0, "description"),
70 gnc_sql_make_table_entry<CT_INT>(
71 "num_periods", 0, COL_NNUL, "num_periods"),
72 };
73
74 static QofInstance* get_budget (gpointer pObj);
75 static void set_budget (gpointer pObj, gpointer val);
76 static QofInstance* get_account (gpointer pObj);
77 static void set_account (gpointer pObj, gpointer val);
78 static gint get_period_num (gpointer pObj);
79 static void set_period_num (gpointer pObj, gpointer val);
80 static gnc_numeric get_amount (gpointer pObj);
81 static void set_amount (gpointer pObj, gnc_numeric value);
82
GncSqlBudgetBackend()83 GncSqlBudgetBackend::GncSqlBudgetBackend() :
84 GncSqlObjectBackend(TABLE_VERSION, GNC_ID_BUDGET,
85 BUDGET_TABLE, col_table) {}
86
87 typedef struct
88 {
89 GncBudget* budget;
90 Account* account;
91 guint period_num;
92 } budget_amount_info_t;
93
94 static const EntryVec budget_amounts_col_table
95 {
96 gnc_sql_make_table_entry<CT_INT>(
97 "id", 0, COL_NNUL | COL_PKEY | COL_AUTOINC),
98 gnc_sql_make_table_entry<CT_BUDGETREF>("budget_guid", 0, COL_NNUL,
99 (QofAccessFunc)get_budget,
100 (QofSetterFunc)set_budget),
101 gnc_sql_make_table_entry<CT_ACCOUNTREF>("account_guid", 0, COL_NNUL,
102 (QofAccessFunc)get_account,
103 (QofSetterFunc)set_account),
104 gnc_sql_make_table_entry<CT_INT>("period_num", 0, COL_NNUL,
105 (QofAccessFunc)get_period_num,
106 (QofSetterFunc)set_period_num),
107 gnc_sql_make_table_entry<CT_NUMERIC>("amount", 0, COL_NNUL,
108 (QofAccessFunc)get_amount,
109 (QofSetterFunc)set_amount),
110 };
111
112 /* ================================================================= */
113 static QofInstance*
get_budget(gpointer pObj)114 get_budget (gpointer pObj)
115 {
116 budget_amount_info_t* info = (budget_amount_info_t*)pObj;
117
118 g_return_val_if_fail (pObj != NULL, NULL);
119
120 return QOF_INSTANCE (info->budget);
121 }
122
123 static void
set_budget(gpointer pObj,gpointer val)124 set_budget (gpointer pObj, gpointer val)
125 {
126 }
127
128 static QofInstance*
get_account(gpointer pObj)129 get_account (gpointer pObj)
130 {
131 budget_amount_info_t* info = (budget_amount_info_t*)pObj;
132
133 g_return_val_if_fail (pObj != NULL, NULL);
134
135 return QOF_INSTANCE (info->account);
136 }
137
138 static void
set_account(gpointer pObj,gpointer val)139 set_account (gpointer pObj, gpointer val)
140 {
141 budget_amount_info_t* info = (budget_amount_info_t*)pObj;
142
143 g_return_if_fail (pObj != NULL);
144 g_return_if_fail (val != NULL);
145 g_return_if_fail (GNC_IS_ACCOUNT (val));
146
147 info->account = GNC_ACCOUNT (val);
148 }
149
150 static gint
get_period_num(gpointer pObj)151 get_period_num (gpointer pObj)
152 {
153 budget_amount_info_t* info = (budget_amount_info_t*)pObj;
154
155 g_return_val_if_fail (pObj != NULL, 0);
156
157 return info->period_num;
158 }
159
160 static void
set_period_num(gpointer pObj,gpointer val)161 set_period_num (gpointer pObj, gpointer val)
162 {
163 budget_amount_info_t* info = (budget_amount_info_t*)pObj;
164
165 g_return_if_fail (pObj != NULL);
166
167 info->period_num = GPOINTER_TO_UINT (val);
168 }
169
170 static gnc_numeric
get_amount(gpointer pObj)171 get_amount (gpointer pObj)
172 {
173 budget_amount_info_t* info = (budget_amount_info_t*)pObj;
174
175 g_return_val_if_fail (pObj != NULL, gnc_numeric_zero ());
176
177 return gnc_budget_get_account_period_value (info->budget, info->account,
178 info->period_num);
179 }
180
181 static void
set_amount(gpointer pObj,gnc_numeric value)182 set_amount (gpointer pObj, gnc_numeric value)
183 {
184 budget_amount_info_t* info = (budget_amount_info_t*)pObj;
185
186 g_return_if_fail (pObj != NULL);
187
188 gnc_budget_set_account_period_value (info->budget, info->account,
189 info->period_num, value);
190 }
191
192 /*----------------------------------------------------------------*/
193 /**
194 * Loads the budget amounts for a budget.
195 *
196 * @param sql_be SQL backend
197 * @param budget Budget
198 */
199 static void
load_budget_amounts(GncSqlBackend * sql_be,GncBudget * budget)200 load_budget_amounts (GncSqlBackend* sql_be, GncBudget* budget)
201 {
202 gchar guid_buf[GUID_ENCODING_LENGTH + 1];
203
204 g_return_if_fail (sql_be != NULL);
205 g_return_if_fail (budget != NULL);
206
207 (void)guid_to_string_buff (qof_instance_get_guid (QOF_INSTANCE (budget)),
208 guid_buf);
209 auto sql = g_strdup_printf ("SELECT * FROM %s WHERE budget_guid='%s'",
210 AMOUNTS_TABLE, guid_buf);
211 auto stmt = sql_be->create_statement_from_sql(sql);
212 g_free (sql);
213 if (stmt != nullptr)
214 {
215 auto result = sql_be->execute_select_statement(stmt);
216 budget_amount_info_t info = { budget, NULL, 0 };
217
218 for (auto row : *result)
219 gnc_sql_load_object (sql_be, row, NULL, &info, budget_amounts_col_table);
220 }
221 }
222
223 /**
224 * Deletes the budget amounts for a budget.
225 *
226 * @param sql_be SQL backend
227 * @param budget Budget
228 */
229 static gboolean
delete_budget_amounts(GncSqlBackend * sql_be,GncBudget * budget)230 delete_budget_amounts (GncSqlBackend* sql_be, GncBudget* budget)
231 {
232 gchar guid_buf[GUID_ENCODING_LENGTH + 1];
233
234 g_return_val_if_fail (sql_be != NULL, FALSE);
235 g_return_val_if_fail (budget != NULL, FALSE);
236
237 (void)guid_to_string_buff (qof_instance_get_guid (QOF_INSTANCE (budget)),
238 guid_buf);
239 std::stringstream sql;
240 sql << "DELETE FROM " << AMOUNTS_TABLE << " WHERE budget_guid='"<<
241 guid_buf << "'";
242 auto stmt = sql_be->create_statement_from_sql(sql.str());
243 sql_be->execute_nonselect_statement(stmt);
244
245 return true;
246 }
247
248 /**
249 * Saves the budget amounts for a budget.
250 *
251 * @param sql_be SQL backend
252 * @param budget Budget
253 */
254 static gboolean
save_budget_amounts(GncSqlBackend * sql_be,GncBudget * budget)255 save_budget_amounts (GncSqlBackend* sql_be, GncBudget* budget)
256 {
257 GList* descendants;
258 GList* node;
259 budget_amount_info_t info;
260 guint num_periods;
261 gboolean is_ok = TRUE;
262
263 g_return_val_if_fail (sql_be != NULL, FALSE);
264 g_return_val_if_fail (budget != NULL, FALSE);
265
266 // Delete the amounts, then save
267 delete_budget_amounts (sql_be, budget);
268
269 info.budget = budget;
270 num_periods = gnc_budget_get_num_periods (budget);
271 descendants = gnc_account_get_descendants (gnc_book_get_root_account (
272 sql_be->book()));
273 for (node = descendants; node != NULL && is_ok; node = g_list_next (node))
274 {
275 guint i;
276
277 info.account = GNC_ACCOUNT (node->data);
278 for (i = 0; i < num_periods && is_ok; i++)
279 {
280 if (gnc_budget_is_account_period_value_set (budget, info.account, i))
281 {
282 info.period_num = i;
283 is_ok = sql_be->do_db_operation(OP_DB_INSERT, AMOUNTS_TABLE,
284 "", &info,
285 budget_amounts_col_table);
286 }
287 }
288 }
289 g_list_free (descendants);
290
291 return is_ok;
292 }
293 /*----------------------------------------------------------------*/
294 static GncBudget*
load_single_budget(GncSqlBackend * sql_be,GncSqlRow & row)295 load_single_budget (GncSqlBackend* sql_be, GncSqlRow& row)
296 {
297 const GncGUID* guid;
298 GncBudget* pBudget = NULL;
299 Recurrence* r;
300
301 g_return_val_if_fail (sql_be != NULL, NULL);
302
303 guid = gnc_sql_load_guid (sql_be, row);
304 if (guid != NULL)
305 {
306 pBudget = gnc_budget_lookup (guid, sql_be->book());
307 }
308 if (pBudget == NULL)
309 {
310 pBudget = gnc_budget_new (sql_be->book());
311 }
312
313 gnc_budget_begin_edit (pBudget);
314 gnc_sql_load_object (sql_be, row, GNC_ID_BUDGET, pBudget, col_table);
315 load_budget_amounts (sql_be, pBudget);
316 r = gnc_sql_recurrence_load (sql_be, gnc_budget_get_guid (pBudget));
317 if (r != NULL)
318 {
319 gnc_budget_set_recurrence (pBudget, r);
320 g_free (r);
321 }
322 gnc_budget_commit_edit (pBudget);
323
324 return pBudget;
325 }
326
327 void
load_all(GncSqlBackend * sql_be)328 GncSqlBudgetBackend::load_all (GncSqlBackend* sql_be)
329 {
330 g_return_if_fail (sql_be != NULL);
331
332 std::string sql("SELECT * FROM " BUDGET_TABLE);
333 auto stmt = sql_be->create_statement_from_sql(sql);
334 auto result = sql_be->execute_select_statement(stmt);
335 for (auto row : *result)
336 load_single_budget (sql_be, row);
337
338 std::string pkey(col_table[0]->name());
339 sql = "SELECT DISTINCT ";
340 sql += pkey + " FROM " BUDGET_TABLE;
341 gnc_sql_slots_load_for_sql_subquery (sql_be, sql,
342 (BookLookupFn)gnc_budget_lookup);
343 }
344
345 /* ================================================================= */
346 void
create_tables(GncSqlBackend * sql_be)347 GncSqlBudgetBackend::create_tables (GncSqlBackend* sql_be)
348 {
349 gint version;
350
351 g_return_if_fail (sql_be != NULL);
352
353 version = sql_be->get_table_version( BUDGET_TABLE);
354 if (version == 0)
355 {
356 (void)sql_be->create_table(BUDGET_TABLE, TABLE_VERSION, col_table);
357 }
358
359 version = sql_be->get_table_version( AMOUNTS_TABLE);
360 if (version == 0)
361 {
362 (void)sql_be->create_table(AMOUNTS_TABLE, AMOUNTS_TABLE_VERSION,
363 budget_amounts_col_table);
364 }
365 }
366
367 /* ================================================================= */
368 bool
commit(GncSqlBackend * sql_be,QofInstance * inst)369 GncSqlBudgetBackend::commit (GncSqlBackend* sql_be, QofInstance* inst)
370 {
371 GncBudget* pBudget = GNC_BUDGET (inst);
372 const GncGUID* guid;
373 E_DB_OPERATION op;
374 gboolean is_infant;
375 gboolean is_ok;
376
377 g_return_val_if_fail (sql_be != NULL, FALSE);
378 g_return_val_if_fail (inst != NULL, FALSE);
379 g_return_val_if_fail (GNC_IS_BUDGET (inst), FALSE);
380
381 is_infant = qof_instance_get_infant (inst);
382 if (qof_instance_get_destroying (inst))
383 {
384 op = OP_DB_DELETE;
385 }
386 else if (sql_be->pristine() || is_infant)
387 {
388 op = OP_DB_INSERT;
389 }
390 else
391 {
392 op = OP_DB_UPDATE;
393 }
394 is_ok = sql_be->do_db_operation(op, BUDGET_TABLE, GNC_ID_BUDGET, pBudget,
395 col_table);
396
397 // Now, commit any slots and recurrence
398 if (is_ok)
399 {
400 guid = qof_instance_get_guid (inst);
401 if (!qof_instance_get_destroying (inst))
402 {
403 is_ok = save_budget_amounts (sql_be, pBudget);
404 if (is_ok)
405 {
406 is_ok = gnc_sql_recurrence_save (sql_be, guid,
407 gnc_budget_get_recurrence (pBudget));
408 }
409 if (is_ok)
410 {
411 is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst);
412 }
413 }
414 else
415 {
416 is_ok = delete_budget_amounts (sql_be, pBudget);
417 if (is_ok)
418 {
419 is_ok = gnc_sql_recurrence_delete (sql_be, guid);
420 }
421 if (is_ok)
422 {
423 (void)gnc_sql_slots_delete (sql_be, guid);
424 }
425 }
426 }
427
428 return is_ok;
429 }
430
431 static void
do_save(QofInstance * inst,gpointer data)432 do_save (QofInstance* inst, gpointer data)
433 {
434 write_objects_t* s = (write_objects_t*)data;
435
436 if (s->is_ok)
437 {
438 s->is_ok = s->obe->commit (s->be, inst);
439 }
440 }
441
442 bool
write(GncSqlBackend * sql_be)443 GncSqlBudgetBackend::write (GncSqlBackend* sql_be)
444 {
445 write_objects_t data;
446
447 g_return_val_if_fail (sql_be != NULL, FALSE);
448
449 data.be = sql_be;
450 data.is_ok = TRUE;
451 data.obe = this;
452 qof_collection_foreach (qof_book_get_collection (sql_be->book(), GNC_ID_BUDGET),
453 (QofInstanceForeachCB)do_save, &data);
454
455 return data.is_ok;
456 }
457
458 /* ================================================================= */
459 template<> void
load(const GncSqlBackend * sql_be,GncSqlRow & row,QofIdTypeConst obj_name,gpointer pObject) const460 GncSqlColumnTableEntryImpl<CT_BUDGETREF>::load (const GncSqlBackend* sql_be,
461 GncSqlRow& row,
462 QofIdTypeConst obj_name,
463 gpointer pObject) const noexcept
464 {
465 load_from_guid_ref(row, obj_name, pObject,
466 [sql_be](GncGUID* g){
467 return gnc_budget_lookup (g, sql_be->book());
468 });
469 }
470
471 template<> void
add_to_table(ColVec & vec) const472 GncSqlColumnTableEntryImpl<CT_BUDGETREF>::add_to_table(ColVec& vec) const noexcept
473 {
474 add_objectref_guid_to_table(vec);
475 }
476
477 template<> void
add_to_query(QofIdTypeConst obj_name,const gpointer pObject,PairVec & vec) const478 GncSqlColumnTableEntryImpl<CT_BUDGETREF>::add_to_query(QofIdTypeConst obj_name,
479 const gpointer pObject,
480 PairVec& vec) const noexcept
481 {
482 add_objectref_guid_to_query(obj_name, pObject, vec);
483 }
484
485 /* ========================== END OF FILE ===================== */
486