GnuCash  5.6-150-g038405b370+
gnc-budget-sql.cpp
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 \********************************************************************/
28 #include <guid.hpp>
29 #include <config.h>
30 
31 #include <glib.h>
32 
33 #include "qof.h"
34 #include "Recurrence.h"
35 #include "gnc-budget.h"
36 
37 #if defined( S_SPLINT_S )
38 #include "splint-defs.h"
39 #endif
40 
41 #include "gnc-sql-connection.hpp"
42 #include "gnc-sql-backend.hpp"
43 #include "gnc-sql-object-backend.hpp"
44 #include "gnc-sql-column-table-entry.hpp"
45 #include "gnc-budget-sql.h"
46 #include "gnc-slots-sql.h"
47 #include "gnc-recurrence-sql.h"
48 
49 #define BUDGET_TABLE "budgets"
50 #define TABLE_VERSION 1
51 #define AMOUNTS_TABLE "budget_amounts"
52 #define AMOUNTS_TABLE_VERSION 1
53 
54 [[maybe_unused]] static QofLogModule log_module = G_LOG_DOMAIN;
55 
56 #define BUDGET_MAX_NAME_LEN 2048
57 #define BUDGET_MAX_DESCRIPTION_LEN 2048
58 
59 static const EntryVec col_table
60 {
61  gnc_sql_make_table_entry<CT_GUID>(
62  "guid", 0, COL_NNUL | COL_PKEY, "guid"),
63  gnc_sql_make_table_entry<CT_STRING>(
64  "name", BUDGET_MAX_NAME_LEN, COL_NNUL, "name"),
65  gnc_sql_make_table_entry<CT_STRING>(
66  "description", BUDGET_MAX_DESCRIPTION_LEN, 0, "description"),
67  gnc_sql_make_table_entry<CT_INT>(
68  "num_periods", 0, COL_NNUL, "num_periods"),
69 };
70 
71 static QofInstance* get_budget (gpointer pObj);
72 static void set_budget (gpointer pObj, gpointer val);
73 static QofInstance* get_account (gpointer pObj);
74 static void set_account (gpointer pObj, gpointer val);
75 static gint get_period_num (gpointer pObj);
76 static void set_period_num (gpointer pObj, gpointer val);
77 static gnc_numeric get_amount (gpointer pObj);
78 static void set_amount (gpointer pObj, gnc_numeric value);
79 
80 GncSqlBudgetBackend::GncSqlBudgetBackend() :
81  GncSqlObjectBackend(TABLE_VERSION, GNC_ID_BUDGET,
82  BUDGET_TABLE, col_table) {}
83 
84 typedef struct
85 {
86  GncBudget* budget;
87  Account* account;
88  guint period_num;
90 
91 static const EntryVec budget_amounts_col_table
92 {
93  gnc_sql_make_table_entry<CT_INT>(
94  "id", 0, COL_NNUL | COL_PKEY | COL_AUTOINC),
95  gnc_sql_make_table_entry<CT_BUDGETREF>("budget_guid", 0, COL_NNUL,
96  (QofAccessFunc)get_budget,
97  (QofSetterFunc)set_budget),
98  gnc_sql_make_table_entry<CT_ACCOUNTREF>("account_guid", 0, COL_NNUL,
99  (QofAccessFunc)get_account,
100  (QofSetterFunc)set_account),
101  gnc_sql_make_table_entry<CT_INT>("period_num", 0, COL_NNUL,
102  (QofAccessFunc)get_period_num,
103  (QofSetterFunc)set_period_num),
104  gnc_sql_make_table_entry<CT_NUMERIC>("amount", 0, COL_NNUL,
105  (QofAccessFunc)get_amount,
106  (QofSetterFunc)set_amount),
107 };
108 
109 /* ================================================================= */
110 static QofInstance*
111 get_budget (gpointer pObj)
112 {
114 
115  g_return_val_if_fail (pObj != NULL, NULL);
116 
117  return QOF_INSTANCE (info->budget);
118 }
119 
120 static void
121 set_budget (gpointer pObj, gpointer val)
122 {
123 }
124 
125 static QofInstance*
126 get_account (gpointer pObj)
127 {
129 
130  g_return_val_if_fail (pObj != NULL, NULL);
131 
132  return QOF_INSTANCE (info->account);
133 }
134 
135 static void
136 set_account (gpointer pObj, gpointer val)
137 {
139 
140  g_return_if_fail (pObj != NULL);
141  g_return_if_fail (val != NULL);
142  g_return_if_fail (GNC_IS_ACCOUNT (val));
143 
144  info->account = GNC_ACCOUNT (val);
145 }
146 
147 static gint
148 get_period_num (gpointer pObj)
149 {
151 
152  g_return_val_if_fail (pObj != NULL, 0);
153 
154  return info->period_num;
155 }
156 
157 static void
158 set_period_num (gpointer pObj, gpointer val)
159 {
161 
162  g_return_if_fail (pObj != NULL);
163 
164  info->period_num = GPOINTER_TO_UINT (val);
165 }
166 
167 static gnc_numeric
168 get_amount (gpointer pObj)
169 {
171 
172  g_return_val_if_fail (pObj != NULL, gnc_numeric_zero ());
173 
174  return gnc_budget_get_account_period_value (info->budget, info->account,
175  info->period_num);
176 }
177 
178 static void
179 set_amount (gpointer pObj, gnc_numeric value)
180 {
182 
183  g_return_if_fail (pObj != NULL);
184 
185  gnc_budget_set_account_period_value (info->budget, info->account,
186  info->period_num, value);
187 }
188 
189 /*----------------------------------------------------------------*/
196 static void
197 load_budget_amounts (GncSqlBackend* sql_be, GncBudget* budget)
198 {
199  gchar guid_buf[GUID_ENCODING_LENGTH + 1];
200 
201  g_return_if_fail (sql_be != NULL);
202  g_return_if_fail (budget != NULL);
203 
204  (void)guid_to_string_buff (qof_instance_get_guid (QOF_INSTANCE (budget)),
205  guid_buf);
206  auto sql = g_strdup_printf ("SELECT * FROM %s WHERE budget_guid='%s'",
207  AMOUNTS_TABLE, guid_buf);
208  auto stmt = sql_be->create_statement_from_sql(sql);
209  g_free (sql);
210  if (stmt != nullptr)
211  {
212  auto result = sql_be->execute_select_statement(stmt);
213  budget_amount_info_t info = { budget, NULL, 0 };
214 
215  for (auto row : *result)
216  gnc_sql_load_object (sql_be, row, NULL, &info, budget_amounts_col_table);
217  }
218 }
219 
226 static gboolean
227 delete_budget_amounts (GncSqlBackend* sql_be, GncBudget* budget)
228 {
229  gchar guid_buf[GUID_ENCODING_LENGTH + 1];
230 
231  g_return_val_if_fail (sql_be != NULL, FALSE);
232  g_return_val_if_fail (budget != NULL, FALSE);
233 
234  (void)guid_to_string_buff (qof_instance_get_guid (QOF_INSTANCE (budget)),
235  guid_buf);
236  std::stringstream sql;
237  sql << "DELETE FROM " << AMOUNTS_TABLE << " WHERE budget_guid='"<<
238  guid_buf << "'";
239  auto stmt = sql_be->create_statement_from_sql(sql.str());
240  sql_be->execute_nonselect_statement(stmt);
241 
242  return true;
243 }
244 
251 static gboolean
252 save_budget_amounts (GncSqlBackend* sql_be, GncBudget* budget)
253 {
254  GList* descendants;
255  GList* node;
257  guint num_periods;
258  gboolean is_ok = TRUE;
259 
260  g_return_val_if_fail (sql_be != NULL, FALSE);
261  g_return_val_if_fail (budget != NULL, FALSE);
262 
263  // Delete the amounts, then save
264  delete_budget_amounts (sql_be, budget);
265 
266  info.budget = budget;
267  num_periods = gnc_budget_get_num_periods (budget);
268  descendants = gnc_account_get_descendants (gnc_book_get_root_account (
269  sql_be->book()));
270  for (node = descendants; node != NULL && is_ok; node = g_list_next (node))
271  {
272  guint i;
273 
274  info.account = GNC_ACCOUNT (node->data);
275  for (i = 0; i < num_periods && is_ok; i++)
276  {
277  if (gnc_budget_is_account_period_value_set (budget, info.account, i))
278  {
279  info.period_num = i;
280  is_ok = sql_be->do_db_operation(OP_DB_INSERT, AMOUNTS_TABLE,
281  "", &info,
282  budget_amounts_col_table);
283  }
284  }
285  }
286  g_list_free (descendants);
287 
288  return is_ok;
289 }
290 /*----------------------------------------------------------------*/
291 static GncBudget*
292 load_single_budget (GncSqlBackend* sql_be, GncSqlRow& row)
293 {
294  const GncGUID* guid;
295  GncBudget* pBudget = NULL;
296  Recurrence* r;
297 
298  g_return_val_if_fail (sql_be != NULL, NULL);
299 
300  guid = gnc_sql_load_guid (sql_be, row);
301  if (guid != NULL)
302  {
303  pBudget = gnc_budget_lookup (guid, sql_be->book());
304  }
305  if (pBudget == NULL)
306  {
307  pBudget = gnc_budget_new (sql_be->book());
308  }
309 
310  gnc_budget_begin_edit (pBudget);
311  gnc_sql_load_object (sql_be, row, GNC_ID_BUDGET, pBudget, col_table);
312  load_budget_amounts (sql_be, pBudget);
313  r = gnc_sql_recurrence_load (sql_be, gnc_budget_get_guid (pBudget));
314  if (r != NULL)
315  {
316  gnc_budget_set_recurrence (pBudget, r);
317  g_free (r);
318  }
319  gnc_budget_commit_edit (pBudget);
320 
321  return pBudget;
322 }
323 
324 void
326 {
327  g_return_if_fail (sql_be != NULL);
328 
329  std::string sql("SELECT * FROM " BUDGET_TABLE);
330  auto stmt = sql_be->create_statement_from_sql(sql);
331  auto result = sql_be->execute_select_statement(stmt);
332  for (auto row : *result)
333  load_single_budget (sql_be, row);
334 
335  std::string pkey(col_table[0]->name());
336  sql = "SELECT DISTINCT ";
337  sql += pkey + " FROM " BUDGET_TABLE;
339  (BookLookupFn)gnc_budget_lookup);
340 }
341 
342 /* ================================================================= */
343 void
345 {
346  gint version;
347 
348  g_return_if_fail (sql_be != NULL);
349 
350  version = sql_be->get_table_version( BUDGET_TABLE);
351  if (version == 0)
352  {
353  (void)sql_be->create_table(BUDGET_TABLE, TABLE_VERSION, col_table);
354  }
355 
356  version = sql_be->get_table_version( AMOUNTS_TABLE);
357  if (version == 0)
358  {
359  (void)sql_be->create_table(AMOUNTS_TABLE, AMOUNTS_TABLE_VERSION,
360  budget_amounts_col_table);
361  }
362 }
363 
364 /* ================================================================= */
365 bool
367 {
368  GncBudget* pBudget = GNC_BUDGET (inst);
369  const GncGUID* guid;
370  E_DB_OPERATION op;
371  gboolean is_infant;
372  gboolean is_ok;
373 
374  g_return_val_if_fail (sql_be != NULL, FALSE);
375  g_return_val_if_fail (inst != NULL, FALSE);
376  g_return_val_if_fail (GNC_IS_BUDGET (inst), FALSE);
377 
378  is_infant = qof_instance_get_infant (inst);
379  if (qof_instance_get_destroying (inst))
380  {
381  op = OP_DB_DELETE;
382  }
383  else if (sql_be->pristine() || is_infant)
384  {
385  op = OP_DB_INSERT;
386  }
387  else
388  {
389  op = OP_DB_UPDATE;
390  }
391  is_ok = sql_be->do_db_operation(op, BUDGET_TABLE, GNC_ID_BUDGET, pBudget,
392  col_table);
393 
394  // Now, commit any slots and recurrence
395  if (is_ok)
396  {
397  guid = qof_instance_get_guid (inst);
398  if (!qof_instance_get_destroying (inst))
399  {
400  is_ok = save_budget_amounts (sql_be, pBudget);
401  if (is_ok)
402  {
403  is_ok = gnc_sql_recurrence_save (sql_be, guid,
404  gnc_budget_get_recurrence (pBudget));
405  }
406  if (is_ok)
407  {
408  is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst);
409  }
410  }
411  else
412  {
413  is_ok = delete_budget_amounts (sql_be, pBudget);
414  if (is_ok)
415  {
416  is_ok = gnc_sql_recurrence_delete (sql_be, guid);
417  }
418  if (is_ok)
419  {
420  (void)gnc_sql_slots_delete (sql_be, guid);
421  }
422  }
423  }
424 
425  return is_ok;
426 }
427 
428 static void
429 do_save (QofInstance* inst, gpointer data)
430 {
431  write_objects_t* s = (write_objects_t*)data;
432 
433  if (s->is_ok)
434  {
435  s->is_ok = s->obe->commit (s->be, inst);
436  }
437 }
438 
439 bool
441 {
442  write_objects_t data;
443 
444  g_return_val_if_fail (sql_be != NULL, FALSE);
445 
446  data.be = sql_be;
447  data.is_ok = TRUE;
448  data.obe = this;
449  qof_collection_foreach (qof_book_get_collection (sql_be->book(), GNC_ID_BUDGET),
450  (QofInstanceForeachCB)do_save, &data);
451 
452  return data.is_ok;
453 }
454 
455 /* ================================================================= */
456 template<> void
458  GncSqlRow& row,
459  QofIdTypeConst obj_name,
460  gpointer pObject) const noexcept
461 {
462  load_from_guid_ref(row, obj_name, pObject,
463  [sql_be](GncGUID* g){
464  return gnc_budget_lookup (g, sql_be->book());
465  });
466 }
467 
468 template<> void
470 {
471  add_objectref_guid_to_table(vec);
472 }
473 
474 template<> void
476  const gpointer pObject,
477  PairVec& vec) const noexcept
478 {
479  add_objectref_guid_to_query(obj_name, pObject, vec);
480 }
481 
482 /* ========================== 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.
bool create_table(const std::string &table_name, const EntryVec &col_table) const noexcept
Creates a table in the database.
GncSqlResultPtr execute_select_statement(const GncSqlStatementPtr &stmt) const noexcept
Executes an SQL SELECT statement and returns the result rows.
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...
#define G_LOG_DOMAIN
Functions providing the SX List as a plugin page.
const gchar * QofIdTypeConst
QofIdTypeConst declaration.
Definition: qofid.h:82
load and save accounts data to SQL
STRUCTS.
GnuCash Budgets.
GncBudget * gnc_budget_new(QofBook *book)
Creates and initializes a Budget.
Definition: gnc-budget.cpp:305
gboolean qof_instance_get_destroying(gconstpointer ptr)
Retrieve the flag that indicates whether or not this object is about to be destroyed.
void(* QofInstanceForeachCB)(QofInstance *, gpointer user_data)
Callback type for qof_collection_foreach.
Definition: qofid.h:146
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 ...
void create_tables(GncSqlBackend *) override
Conditionally create or update a database table from m_col_table.
gchar * guid_to_string_buff(const GncGUID *guid, gchar *str)
The guid_to_string_buff() routine puts a null-terminated string encoding of the id into the memory po...
Definition: guid.cpp:173
bool write(GncSqlBackend *) override
Write all objects of m_type_name to the database.
void load_all(GncSqlBackend *) override
Load all objects of m_type in the database into memory.
void(* QofSetterFunc)(gpointer, gpointer)
The QofSetterFunc defines an function pointer for parameter setters.
Definition: qofclass.h:185
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.
load and save accounts data to SQL
Row of SQL Query results.
#define GUID_ENCODING_LENGTH
Number of characters needed to encode a guid as a string not including the null terminator.
Definition: guid.h:84
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...
GList * gnc_account_get_descendants(const Account *account)
This routine returns a flat list of all of the accounts that are descendants of the specified account...
Definition: Account.cpp:2989
Data-passing struct for callbacks to qof_object_foreach() used in GncSqlObjectBackend::write().
gboolean gnc_sql_slots_delete(GncSqlBackend *sql_be, const GncGUID *guid)
gnc_sql_slots_delete - Deletes slots for an object from the db.
void add_to_table(ColVec &vec) const noexcept override
Add a GncSqlColumnInfo structure for the column type to a ColVec.
QofCollection * qof_book_get_collection(const QofBook *book, QofIdType entity_type)
Return The table of entities of the given type.
Definition: qofbook.cpp:521
load and save data to SQL
The type used to store guids in C.
Definition: guid.h:75
bool commit(GncSqlBackend *sql_be, QofInstance *inst) override
UPDATE/INSERT a single instance of m_type_name into the database.
uint_t get_table_version(const std::string &table_name) const noexcept
Returns the version number for a DB table.
Main SQL backend structure.