GnuCash  5.6-150-g038405b370+
gnc-recurrence-sql.cpp
1 /********************************************************************
2  * gnc-recurrence-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 <glib.h>
29 
30 #include <config.h>
31 
32 #include "qof.h"
33 #include "gnc-engine.h"
34 #include "Recurrence.h"
35 
36 #if defined( S_SPLINT_S )
37 #include "splint-defs.h"
38 #endif
39 
40 #include "gnc-sql-connection.hpp"
41 #include "gnc-sql-backend.hpp"
42 #include "gnc-sql-object-backend.hpp"
43 #include "gnc-sql-column-table-entry.hpp"
44 #include "gnc-recurrence-sql.h"
45 
46 G_GNUC_UNUSED static QofLogModule log_module = G_LOG_DOMAIN;
47 
48 #define TABLE_NAME "recurrences"
49 #define TABLE_VERSION 2
50 
51 #define BUDGET_MAX_RECURRENCE_PERIOD_TYPE_LEN 2048
52 #define BUDGET_MAX_RECURRENCE_WEEKEND_ADJUST_LEN 2048
53 
54 typedef struct
55 {
56  GncSqlBackend* be;
57  const GncGUID* guid;
58  Recurrence* pRecurrence;
60 
61 static gpointer get_obj_guid (gpointer pObject);
62 static void set_obj_guid (void);
63 static gint get_recurrence_mult (gpointer pObject);
64 static void set_recurrence_mult (gpointer pObject, gint value);
65 static gpointer get_recurrence_period_type (gpointer pObject);
66 static void set_recurrence_period_type (gpointer pObject, gpointer pValue);
67 static gpointer get_recurrence_weekend_adjust (gpointer pObject);
68 static void set_recurrence_weekend_adjust (gpointer pObject, gpointer pValue);
69 static gpointer get_recurrence_period_start (gpointer pObject);
70 static void set_recurrence_period_start (gpointer pObject, gpointer pValue);
71 
72 static const EntryVec col_table
73 ({
74  gnc_sql_make_table_entry<CT_INT>(
75  "id", 0, COL_PKEY | COL_NNUL | COL_AUTOINC),
76  gnc_sql_make_table_entry<CT_GUID>("obj_guid", 0, COL_NNUL,
77  (QofAccessFunc)get_obj_guid, (QofSetterFunc)set_obj_guid),
78  gnc_sql_make_table_entry<CT_INT>(
79  "recurrence_mult", 0, COL_NNUL,
80  (QofAccessFunc)get_recurrence_mult, (QofSetterFunc)set_recurrence_mult),
81  gnc_sql_make_table_entry<CT_STRING>(
82  "recurrence_period_type", BUDGET_MAX_RECURRENCE_PERIOD_TYPE_LEN,
83  COL_NNUL,
84  (QofAccessFunc)get_recurrence_period_type, set_recurrence_period_type),
85  gnc_sql_make_table_entry<CT_GDATE>(
86  "recurrence_period_start", 0, COL_NNUL,
87  (QofAccessFunc)get_recurrence_period_start,
88  set_recurrence_period_start),
89  gnc_sql_make_table_entry<CT_STRING>(
90  "recurrence_weekend_adjust", BUDGET_MAX_RECURRENCE_WEEKEND_ADJUST_LEN,
91  COL_NNUL,
92  (QofAccessFunc)get_recurrence_weekend_adjust,
93  set_recurrence_weekend_adjust)
94 });
95 
96 /* Special column table because we need to be able to access the table by
97 a column other than the primary key */
98 static const EntryVec guid_col_table
99 ({
100  gnc_sql_make_table_entry<CT_GUID>("obj_guid", 0, 0,
101  (QofAccessFunc)get_obj_guid,
102  (QofSetterFunc)set_obj_guid)
103 });
104 
105 /* Special column table used to upgrade table from version 1 to 2 */
106 static const EntryVec weekend_adjust_col_table
107 ({
108  gnc_sql_make_table_entry<CT_STRING>(
109  "recurrence_weekend_adjust", BUDGET_MAX_RECURRENCE_WEEKEND_ADJUST_LEN, 0)
110 });
111 
117  GncSqlObjectBackend(TABLE_VERSION, GNC_ID_ACCOUNT, TABLE_NAME, col_table) {}
118 
119 /* ================================================================= */
120 
121 static gpointer
122 get_obj_guid (gpointer pObject)
123 {
124  recurrence_info_t* pInfo = (recurrence_info_t*)pObject;
125 
126  g_return_val_if_fail (pObject != NULL, NULL);
127 
128  return (gpointer)pInfo->guid;
129 }
130 
131 static void
132 set_obj_guid (void)
133 {
134  // Nowhere to put the GncGUID
135 }
136 
137 static gint
138 get_recurrence_mult (gpointer pObject)
139 {
140  recurrence_info_t* pInfo = (recurrence_info_t*)pObject;
141 
142  g_return_val_if_fail (pObject != NULL, 0);
143  g_return_val_if_fail (pInfo->pRecurrence != NULL, 0);
144 
145  return (gint)pInfo->pRecurrence->mult;
146 }
147 
148 static void
149 set_recurrence_mult (gpointer pObject, gint value)
150 {
151  recurrence_info_t* pInfo = (recurrence_info_t*)pObject;
152 
153  g_return_if_fail (pObject != NULL);
154  g_return_if_fail (pInfo->pRecurrence != NULL);
155 
156  pInfo->pRecurrence->mult = (guint16)value;
157 }
158 
159 static gpointer
160 get_recurrence_period_type (gpointer pObject)
161 {
162  recurrence_info_t* pInfo = (recurrence_info_t*)pObject;
163 
164  g_return_val_if_fail (pObject != NULL, NULL);
165  g_return_val_if_fail (pInfo->pRecurrence != NULL, NULL);
166 
167  return (gpointer)recurrencePeriodTypeToString (
168  recurrenceGetPeriodType (pInfo->pRecurrence));
169 }
170 
171 static void
172 set_recurrence_period_type (gpointer pObject, gpointer pValue)
173 {
174  recurrence_info_t* pInfo = (recurrence_info_t*)pObject;
175 
176  g_return_if_fail (pObject != NULL);
177  g_return_if_fail (pInfo->pRecurrence != NULL);
178  g_return_if_fail (pValue != NULL);
179 
180  pInfo->pRecurrence->ptype = recurrencePeriodTypeFromString ((gchar*)pValue);
181 }
182 
183 static gpointer
184 get_recurrence_weekend_adjust (gpointer pObject)
185 {
186  recurrence_info_t* pInfo = (recurrence_info_t*)pObject;
187 
188  g_return_val_if_fail (pObject != NULL, NULL);
189  g_return_val_if_fail (pInfo->pRecurrence != NULL, NULL);
190 
191  return (gpointer)recurrenceWeekendAdjustToString (
192  recurrenceGetWeekendAdjust (pInfo->pRecurrence));
193 }
194 
195 static void
196 set_recurrence_weekend_adjust (gpointer pObject, gpointer pValue)
197 {
198  recurrence_info_t* pInfo = (recurrence_info_t*)pObject;
199 
200  g_return_if_fail (pObject != NULL);
201  g_return_if_fail (pInfo->pRecurrence != NULL);
202  g_return_if_fail (pValue != NULL);
203 
204  pInfo->pRecurrence->wadj = recurrenceWeekendAdjustFromString ((gchar*)pValue);
205 }
206 
207 static gpointer
208 get_recurrence_period_start (gpointer pObject)
209 {
210  recurrence_info_t* pInfo = (recurrence_info_t*)pObject;
211  static GDate date;
212 
213  g_return_val_if_fail (pObject != NULL, NULL);
214  g_return_val_if_fail (pInfo->pRecurrence != NULL, NULL);
215 
216  date = recurrenceGetDate (pInfo->pRecurrence);
217  return (gpointer)&date;
218 }
219 
220 static void
221 set_recurrence_period_start (gpointer pObject, gpointer pValue)
222 {
223  recurrence_info_t* pInfo = (recurrence_info_t*)pObject;
224  GDate* date = (GDate*)pValue;
225 
226  g_return_if_fail (pObject != NULL);
227  g_return_if_fail (pInfo->pRecurrence != NULL);
228  g_return_if_fail (pValue != NULL);
229 
230  pInfo->pRecurrence->start = *date;
231 }
232 
233 /* ================================================================= */
234 
235 gboolean
236 gnc_sql_recurrence_save (GncSqlBackend* sql_be, const GncGUID* guid,
237  const Recurrence* r)
238 {
239  recurrence_info_t recurrence_info;
240 
241  g_return_val_if_fail (sql_be != NULL, FALSE);
242  g_return_val_if_fail (guid != NULL, FALSE);
243  g_return_val_if_fail (r != NULL, FALSE);
244 
245  (void)gnc_sql_recurrence_delete (sql_be, guid);
246 
247  recurrence_info.be = sql_be;
248  recurrence_info.guid = guid;
249  recurrence_info.pRecurrence = (Recurrence*)r;
250  return sql_be->do_db_operation(OP_DB_INSERT, TABLE_NAME,
251  TABLE_NAME, &recurrence_info, col_table);
252 }
253 
254 void
255 gnc_sql_recurrence_save_list (GncSqlBackend* sql_be, const GncGUID* guid,
256  GList* schedule)
257 {
258  recurrence_info_t recurrence_info;
259  GList* l;
260 
261  g_return_if_fail (sql_be != NULL);
262  g_return_if_fail (guid != NULL);
263 
264  (void)gnc_sql_recurrence_delete (sql_be, guid);
265 
266  recurrence_info.be = sql_be;
267  recurrence_info.guid = guid;
268  for (l = schedule; l != NULL; l = g_list_next (l))
269  {
270  recurrence_info.pRecurrence = (Recurrence*)l->data;
271  (void)sql_be->do_db_operation(OP_DB_INSERT, TABLE_NAME,
272  TABLE_NAME, &recurrence_info, col_table);
273  }
274 }
275 
276 gboolean
277 gnc_sql_recurrence_delete (GncSqlBackend* sql_be, const GncGUID* guid)
278 {
279  recurrence_info_t recurrence_info;
280 
281  g_return_val_if_fail (sql_be != NULL, FALSE);
282  g_return_val_if_fail (guid != NULL, FALSE);
283 
284  recurrence_info.be = sql_be;
285  recurrence_info.guid = guid;
286  return sql_be->do_db_operation(OP_DB_DELETE, TABLE_NAME,
287  TABLE_NAME, &recurrence_info, guid_col_table);
288 }
289 
290 static void
291 load_recurrence (GncSqlBackend* sql_be, GncSqlRow& row, Recurrence* r)
292 {
293  recurrence_info_t recurrence_info;
294 
295  g_return_if_fail (sql_be != NULL);
296  g_return_if_fail (r != NULL);
297 
298  recurrence_info.be = sql_be;
299  recurrence_info.pRecurrence = r;
300 
301  gnc_sql_load_object (sql_be, row, TABLE_NAME, &recurrence_info, col_table);
302 }
303 
304 static GncSqlResultPtr
305 gnc_sql_set_recurrences_from_db (GncSqlBackend* sql_be, const GncGUID* guid)
306 {
307  gchar* buf;
308  gchar guid_buf[GUID_ENCODING_LENGTH + 1];
309 
310  g_return_val_if_fail (sql_be != NULL, NULL);
311  g_return_val_if_fail (guid != NULL, NULL);
312 
313  (void)guid_to_string_buff (guid, guid_buf);
314  buf = g_strdup_printf ("SELECT * FROM %s WHERE obj_guid='%s'", TABLE_NAME,
315  guid_buf);
316  auto stmt = sql_be->create_statement_from_sql (buf);
317  g_free (buf);
318  auto result = sql_be->execute_select_statement(stmt);
319  return result;
320 }
321 
322 Recurrence*
323 gnc_sql_recurrence_load (GncSqlBackend* sql_be, const GncGUID* guid)
324 {
325  Recurrence* r = NULL;
326 
327  g_return_val_if_fail (sql_be != NULL, NULL);
328  g_return_val_if_fail (guid != NULL, NULL);
329 
330  auto result = gnc_sql_set_recurrences_from_db (sql_be, guid);
331  auto row = result->begin();
332  if (row == nullptr)
333  {
334  g_warning ("No recurrences found");
335  return r;
336  }
337  r = g_new0 (Recurrence, 1);
338  g_assert (r != NULL);
339  load_recurrence (sql_be, *(result->begin()), r);
340 
341  if (++row != nullptr)
342  g_warning ("More than 1 recurrence found: first one used");
343 
344  return r;
345 }
346 
347 GList*
348 gnc_sql_recurrence_load_list (GncSqlBackend* sql_be, const GncGUID* guid)
349 {
350  GList* list = NULL;
351 
352  g_return_val_if_fail (sql_be != NULL, NULL);
353  g_return_val_if_fail (guid != NULL, NULL);
354 
355  auto result = gnc_sql_set_recurrences_from_db (sql_be, guid);
356  for (auto row : *result)
357  {
358  Recurrence* pRecurrence = g_new0 (Recurrence, 1);
359  g_assert (pRecurrence != NULL);
360  load_recurrence (sql_be, row, pRecurrence);
361  list = g_list_append (list, pRecurrence);
362  }
363 
364  return list;
365 }
366 
367 /* ================================================================= */
368 static void
369 upgrade_recurrence_table_1_2 (GncSqlBackend* sql_be)
370 {
371  /* Step 1: add field, but allow it to be null */
372  gboolean ok = sql_be->add_columns_to_table(TABLE_NAME,
373  weekend_adjust_col_table);
374  if (!ok)
375  {
376  PERR ("Unable to add recurrence_weekend_adjust column\n");
377  return;
378  }
379 
380  /* Step 2: insert a default value in the newly created column */
381  {
382  const gchar* weekend_adj_str = recurrenceWeekendAdjustToString (WEEKEND_ADJ_NONE);
383  std::stringstream sql;
384  sql << "UPDATE " << TABLE_NAME << " SET " <<
385  weekend_adjust_col_table[0]->name() << "='" <<
386  weekend_adj_str << "'";
387  auto stmt = sql_be->create_statement_from_sql(sql.str());
388  sql_be->execute_nonselect_statement(stmt);
389  }
390 
391  /* Step 3: rewrite the table, requiring the weekend_adj column to be non-null */
392  sql_be->upgrade_table(TABLE_NAME, col_table);
393 
394 }
395 
396 void
398 {
399  gint version;
400 
401  g_return_if_fail (sql_be != NULL);
402 
403  version = sql_be->get_table_version( TABLE_NAME);
404  if (version == 0)
405  {
406  (void)sql_be->create_table(TABLE_NAME, TABLE_VERSION, col_table);
407  }
408  else if (version < TABLE_VERSION)
409  {
410  /* Upgrade:
411  1->2: Add recurrence_weekend_adjust field (mandatory, non-null field)
412  */
413  if (version < m_version)
414  {
415  upgrade_recurrence_table_1_2 (sql_be);
416  }
417  sql_be->set_table_version (TABLE_NAME, TABLE_VERSION);
418  PINFO ("Recurrence table upgraded from version %d to version %d\n", version,
419  TABLE_VERSION);
420  }
421 }
422 
423 /* ========================== 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 add_columns_to_table(const std::string &table_name, const EntryVec &col_table) const noexcept
Adds one or more columns to an existing table.
bool create_table(const std::string &table_name, const EntryVec &col_table) const noexcept
Creates a table in the database.
bool set_table_version(const std::string &table_name, uint_t version) noexcept
Registers the version for a table.
GncSqlResultPtr execute_select_statement(const GncSqlStatementPtr &stmt) const noexcept
Executes an SQL SELECT statement and returns the result rows.
#define G_LOG_DOMAIN
Functions providing the SX List as a plugin page.
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
GncSqlRecurrenceBackend()
Recurrences are neither loadable nor committable.
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
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
void(* QofSetterFunc)(gpointer, gpointer)
The QofSetterFunc defines an function pointer for parameter setters.
Definition: qofclass.h:185
load and save accounts data to SQL
Row of SQL Query results.
void upgrade_table(const std::string &table_name, const EntryVec &col_table) noexcept
Upgrades a table to a new structure.
#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...
All type declarations for the whole Gnucash engine.
void create_tables(GncSqlBackend *) override
Conditionally create or update a database table from m_col_table.
Pure virtual class to iterate over a query result set.
The type used to store guids in C.
Definition: guid.h:75
uint_t get_table_version(const std::string &table_name) const noexcept
Returns the version number for a DB table.
Main SQL backend structure.