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