Branch data Line data Source code
1 : : /********************************************************************
2 : : * gnc-transaction-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-transaction-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/gi18n.h>
32 : :
33 : : #include "qof.h"
34 : : #include "qofquery-p.h"
35 : : #include "qofquerycore-p.h"
36 : :
37 : : #include "Account.h"
38 : : #include "Transaction.h"
39 : : #include <TransactionP.hpp>
40 : : #include <Scrub.h>
41 : : #include "gnc-lot.h"
42 : : #include "engine-helpers.h"
43 : : #include "gnc-commodity.h"
44 : : #include "gnc-engine.h"
45 : :
46 : : #ifdef S_SPLINT_S
47 : : #include "splint-defs.h"
48 : : #endif
49 : :
50 : : #include <string>
51 : : #include <sstream>
52 : :
53 : : #include "escape.h"
54 : :
55 : : #include <gnc-datetime.hpp>
56 : : #include "gnc-sql-connection.hpp"
57 : : #include "gnc-sql-backend.hpp"
58 : : #include "gnc-sql-object-backend.hpp"
59 : : #include "gnc-sql-column-table-entry.hpp"
60 : : #include "gnc-transaction-sql.h"
61 : : #include "gnc-commodity-sql.h"
62 : : #include "gnc-slots-sql.h"
63 : :
64 : : #define SIMPLE_QUERY_COMPILATION 1
65 : :
66 : : static QofLogModule log_module = G_LOG_DOMAIN;
67 : :
68 : : #define TRANSACTION_TABLE "transactions"
69 : : #define TX_TABLE_VERSION 4
70 : : #define SPLIT_TABLE "splits"
71 : : #define SPLIT_TABLE_VERSION 5
72 : :
73 : : struct split_info_t : public write_objects_t
74 : : {
75 : 0 : split_info_t () = default;
76 : : split_info_t (GncSqlBackend* sql_be, bool o,
77 : : GncSqlObjectBackend* e, const GncGUID* g):
78 : : write_objects_t(sql_be, o, e), guid{g} {}
79 : : const GncGUID* guid;
80 : : };
81 : :
82 : : #define TX_MAX_NUM_LEN 2048
83 : : #define TX_MAX_DESCRIPTION_LEN 2048
84 : :
85 : : static const EntryVec tx_col_table
86 : : {
87 : : gnc_sql_make_table_entry<CT_GUID>("guid", 0, COL_NNUL | COL_PKEY, "guid"),
88 : : gnc_sql_make_table_entry<CT_COMMODITYREF>("currency_guid", 0, COL_NNUL,
89 : : "currency"),
90 : : gnc_sql_make_table_entry<CT_STRING>("num", TX_MAX_NUM_LEN, COL_NNUL, "num"),
91 : : gnc_sql_make_table_entry<CT_TIME>("post_date", 0, 0, "post-date"),
92 : : gnc_sql_make_table_entry<CT_TIME>("enter_date", 0, 0, "enter-date"),
93 : : gnc_sql_make_table_entry<CT_STRING>("description", TX_MAX_DESCRIPTION_LEN,
94 : : 0, "description"),
95 : : };
96 : :
97 : : static gpointer get_split_reconcile_state (gpointer pObject);
98 : : static void set_split_reconcile_state (gpointer pObject, gpointer pValue);
99 : : static void set_split_lot (gpointer pObject, gpointer pLot);
100 : :
101 : : #define SPLIT_MAX_MEMO_LEN 2048
102 : : #define SPLIT_MAX_ACTION_LEN 2048
103 : :
104 : : static const EntryVec split_col_table
105 : : {
106 : : gnc_sql_make_table_entry<CT_GUID>("guid", 0, COL_NNUL | COL_PKEY, "guid"),
107 : : gnc_sql_make_table_entry<CT_TXREF>("tx_guid", 0, COL_NNUL, "transaction"),
108 : : gnc_sql_make_table_entry<CT_ACCOUNTREF>("account_guid", 0, COL_NNUL,
109 : : "account"),
110 : : gnc_sql_make_table_entry<CT_STRING>("memo", SPLIT_MAX_MEMO_LEN, COL_NNUL,
111 : : "memo"),
112 : : gnc_sql_make_table_entry<CT_STRING>("action", SPLIT_MAX_ACTION_LEN,
113 : : COL_NNUL, "action"),
114 : : gnc_sql_make_table_entry<CT_STRING>("reconcile_state", 1, COL_NNUL,
115 : : (QofAccessFunc)get_split_reconcile_state,
116 : : set_split_reconcile_state),
117 : : gnc_sql_make_table_entry<CT_TIME>("reconcile_date", 0, 0,
118 : : "reconcile-date"),
119 : : gnc_sql_make_table_entry<CT_NUMERIC>("value", 0, COL_NNUL, "value"),
120 : : gnc_sql_make_table_entry<CT_NUMERIC>("quantity", 0, COL_NNUL, "amount"),
121 : : gnc_sql_make_table_entry<CT_LOTREF>("lot_guid", 0, 0,
122 : : (QofAccessFunc)xaccSplitGetLot,
123 : : set_split_lot),
124 : : };
125 : :
126 : : static const EntryVec post_date_col_table
127 : : {
128 : : gnc_sql_make_table_entry<CT_TIME>("post_date", 0, 0, "post-date"),
129 : : };
130 : :
131 : : static const EntryVec account_guid_col_table
132 : : {
133 : : gnc_sql_make_table_entry<CT_ACCOUNTREF>("account_guid", 0, COL_NNUL,
134 : : "account"),
135 : : };
136 : :
137 : : static const EntryVec tx_guid_col_table
138 : : {
139 : : gnc_sql_make_table_entry<CT_GUID>("tx_guid", 0, 0, "guid"),
140 : : };
141 : :
142 : 10 : GncSqlTransBackend::GncSqlTransBackend() :
143 : : GncSqlObjectBackend(TX_TABLE_VERSION, GNC_ID_TRANS,
144 : 10 : TRANSACTION_TABLE, tx_col_table) {}
145 : :
146 : 10 : GncSqlSplitBackend::GncSqlSplitBackend() :
147 : : GncSqlObjectBackend(SPLIT_TABLE_VERSION, GNC_ID_SPLIT,
148 : 10 : SPLIT_TABLE, split_col_table) {}
149 : :
150 : : /* These functions exist but have not been tested.
151 : : #if LOAD_TRANSACTIONS_AS_NEEDED
152 : : compile_split_query,
153 : : run_split_query,
154 : : free_split_query,
155 : : */
156 : :
157 : : /* ================================================================= */
158 : :
159 : : static gpointer
160 : 18 : get_split_reconcile_state (gpointer pObject)
161 : : {
162 : : static gchar c[2];
163 : :
164 : 18 : g_return_val_if_fail (pObject != NULL, NULL);
165 : 18 : g_return_val_if_fail (GNC_IS_SPLIT (pObject), NULL);
166 : :
167 : 18 : c[0] = xaccSplitGetReconcile (GNC_SPLIT (pObject));
168 : 18 : c[1] = '\0';
169 : 18 : return (gpointer)c;
170 : : }
171 : :
172 : : static void
173 : 18 : set_split_reconcile_state (gpointer pObject, gpointer pValue)
174 : : {
175 : 18 : const gchar* s = (const gchar*)pValue;
176 : :
177 : 18 : g_return_if_fail (pObject != NULL);
178 : 18 : g_return_if_fail (GNC_IS_SPLIT (pObject));
179 : 18 : g_return_if_fail (pValue != NULL);
180 : :
181 : 18 : xaccSplitSetReconcile (GNC_SPLIT (pObject), s[0]);
182 : : }
183 : :
184 : : static void
185 : 3 : set_split_lot (gpointer pObject, gpointer pLot)
186 : : {
187 : : GNCLot* lot;
188 : : Split* split;
189 : :
190 : 3 : g_return_if_fail (pObject != NULL);
191 : 3 : g_return_if_fail (GNC_IS_SPLIT (pObject));
192 : :
193 : 3 : if (pLot == NULL) return;
194 : :
195 : 3 : g_return_if_fail (GNC_IS_LOT (pLot));
196 : :
197 : 3 : split = GNC_SPLIT (pObject);
198 : 3 : lot = GNC_LOT (pLot);
199 : 3 : gnc_lot_add_split (lot, split);
200 : : }
201 : :
202 : : static Split*
203 : 18 : load_single_split (GncSqlBackend* sql_be, GncSqlRow& row)
204 : : {
205 : : const GncGUID* guid;
206 : : GncGUID split_guid;
207 : 18 : Split* pSplit = NULL;
208 : :
209 : 18 : g_return_val_if_fail (sql_be != NULL, NULL);
210 : :
211 : 18 : guid = gnc_sql_load_guid (sql_be, row);
212 : 18 : if (guid == NULL) return NULL;
213 : 18 : if (guid_equal (guid, guid_null ()))
214 : : {
215 : 0 : PWARN ("Bad GUID, creating new");
216 : 0 : split_guid = guid_new_return ();
217 : : }
218 : : else
219 : : {
220 : 18 : split_guid = *guid;
221 : 18 : pSplit = xaccSplitLookup (&split_guid, sql_be->book());
222 : : }
223 : :
224 : 18 : if (pSplit)
225 : 0 : return pSplit; //Already loaded, nothing to do.
226 : :
227 : 18 : pSplit = xaccMallocSplit (sql_be->book());
228 : 18 : gnc_sql_load_object (sql_be, row, GNC_ID_SPLIT, pSplit, split_col_table);
229 : :
230 : : /*# -ifempty */
231 : 18 : if (pSplit != xaccSplitLookup (&split_guid, sql_be->book()))
232 : : {
233 : : gchar guidstr[GUID_ENCODING_LENGTH + 1];
234 : 0 : guid_to_string_buff (qof_instance_get_guid (pSplit), guidstr);
235 : 0 : PERR ("A malformed split with id %s was found in the dataset.", guidstr);
236 : 0 : qof_backend_set_error ((QofBackend*)sql_be, ERR_BACKEND_DATA_CORRUPT);
237 : 0 : pSplit = NULL;
238 : : }
239 : 18 : if (!xaccSplitGetAccount(pSplit))
240 : : {
241 : : gchar guidstr[GUID_ENCODING_LENGTH + 1];
242 : 0 : guid_to_string_buff (qof_instance_get_guid (pSplit), guidstr);
243 : 0 : PERR("Split %s created with no account!", guidstr);
244 : : }
245 : 18 : return pSplit;
246 : : }
247 : : static void
248 : 4 : load_splits_for_transactions (GncSqlBackend* sql_be, std::string selector)
249 : : {
250 : 4 : g_return_if_fail (sql_be != NULL);
251 : :
252 : 4 : const std::string spkey(split_col_table[0]->name());
253 : 4 : const std::string sskey(tx_guid_col_table[0]->name());
254 : 4 : const std::string tpkey(tx_col_table[0]->name());
255 : :
256 : 4 : std::string sql("SELECT ");
257 : 4 : if (selector.empty())
258 : : {
259 : : sql += SPLIT_TABLE ".* FROM " SPLIT_TABLE " INNER JOIN "
260 : 8 : TRANSACTION_TABLE " ON " SPLIT_TABLE "." + sskey + " = "
261 : 4 : TRANSACTION_TABLE "." + tpkey;
262 : 4 : selector = "(SELECT DISTINCT " + tpkey + " FROM " TRANSACTION_TABLE ")";
263 : : }
264 : : else
265 : 0 : sql += " * FROM " SPLIT_TABLE " WHERE " + sskey + " IN " + selector;
266 : :
267 : : // Execute the query and load the splits
268 : 4 : auto stmt = sql_be->create_statement_from_sql(sql);
269 : 4 : auto result = sql_be->execute_select_statement (stmt);
270 : :
271 : 22 : for (auto row : *result)
272 : 22 : load_single_split (sql_be, row);
273 : 4 : sql = "SELECT DISTINCT ";
274 : 4 : sql += spkey + " FROM " SPLIT_TABLE " WHERE " + sskey + " IN " + selector;
275 : 4 : gnc_sql_slots_load_for_sql_subquery(sql_be, sql,
276 : : (BookLookupFn)xaccSplitLookup);
277 : 4 : }
278 : :
279 : : static Transaction*
280 : 11 : load_single_tx (GncSqlBackend* sql_be, GncSqlRow& row)
281 : : {
282 : : const GncGUID* guid;
283 : : GncGUID tx_guid;
284 : : Transaction* pTx;
285 : :
286 : 11 : g_return_val_if_fail (sql_be != NULL, NULL);
287 : :
288 : 11 : guid = gnc_sql_load_guid (sql_be, row);
289 : 11 : if (guid == NULL) return NULL;
290 : 11 : tx_guid = *guid;
291 : :
292 : 11 : pTx = xaccTransLookup (&tx_guid, sql_be->book());
293 : 11 : if (pTx)
294 : 2 : return nullptr; // Nothing to do.
295 : :
296 : 9 : pTx = xaccMallocTransaction (sql_be->book());
297 : 9 : xaccTransBeginEdit (pTx);
298 : 9 : gnc_sql_load_object (sql_be, row, GNC_ID_TRANS, pTx, tx_col_table);
299 : :
300 : 9 : if (pTx != xaccTransLookup (&tx_guid, sql_be->book()))
301 : : {
302 : : gchar guidstr[GUID_ENCODING_LENGTH + 1];
303 : 0 : guid_to_string_buff (qof_instance_get_guid (pTx), guidstr);
304 : 0 : PERR ("A malformed transaction with id %s was found in the dataset.", guidstr);
305 : 0 : qof_backend_set_error ((QofBackend*)sql_be, ERR_BACKEND_DATA_CORRUPT);
306 : 0 : pTx = NULL;
307 : : }
308 : :
309 : 9 : return pTx;
310 : : }
311 : :
312 : : /**
313 : : * Structure to hold start/end balances for each account. The values are
314 : : * saved before splits are loaded, and then used to adjust the start balances
315 : : * so that the end balances (which are calculated and correct on initial load)
316 : : * are unchanged.
317 : : */
318 : : typedef struct
319 : : {
320 : : Account* acc;
321 : : gnc_numeric start_bal;
322 : : gnc_numeric end_bal;
323 : : gnc_numeric start_cleared_bal;
324 : : gnc_numeric end_cleared_bal;
325 : : gnc_numeric start_reconciled_bal;
326 : : gnc_numeric end_reconciled_bal;
327 : : } full_acct_balances_t;
328 : :
329 : : /**
330 : : * Executes a transaction query statement and loads the transactions and all
331 : : * of the splits.
332 : : *
333 : : * @param sql_be SQL backend
334 : : * @param stmt SQL statement
335 : : */
336 : : static void
337 : 10 : query_transactions (GncSqlBackend* sql_be, std::string selector)
338 : : {
339 : 14 : g_return_if_fail (sql_be != NULL);
340 : :
341 : 10 : const std::string tpkey(tx_col_table[0]->name());
342 : 10 : std::string sql("SELECT * FROM " TRANSACTION_TABLE);
343 : :
344 : 10 : if (!selector.empty() && selector[0] == '(')
345 : 3 : sql += " WHERE " + tpkey + " IN " + selector;
346 : 7 : else if (!selector.empty()) // plain condition
347 : 1 : sql += " WHERE " + selector;
348 : 10 : auto stmt = sql_be->create_statement_from_sql(sql);
349 : 10 : auto result = sql_be->execute_select_statement(stmt);
350 : 10 : if (result->begin() == result->end())
351 : : {
352 : 4 : PINFO("Query %s returned no results", sql.c_str());
353 : 4 : return;
354 : : }
355 : :
356 : : Transaction* tx;
357 : :
358 : : // Load the transactions
359 : 6 : InstanceVec instances;
360 : 6 : instances.reserve(result->size());
361 : 17 : for (auto row : *result)
362 : : {
363 : 11 : tx = load_single_tx (sql_be, row);
364 : 11 : if (tx != nullptr)
365 : : {
366 : 9 : xaccTransScrubPostedDate (tx);
367 : 9 : instances.push_back(QOF_INSTANCE(tx));
368 : : }
369 : 17 : }
370 : :
371 : : // Load all splits and slots for the transactions
372 : 6 : if (!instances.empty())
373 : : {
374 : 4 : const std::string tpkey(tx_col_table[0]->name());
375 : 4 : if (!selector.empty() && (selector[0] != '('))
376 : : {
377 : 0 : auto tselector = std::string ("(SELECT DISTINCT ");
378 : 0 : tselector += tpkey + " FROM " TRANSACTION_TABLE " WHERE " + selector + ")";
379 : 0 : selector = tselector;
380 : 0 : }
381 : :
382 : 4 : load_splits_for_transactions (sql_be, selector);
383 : :
384 : 4 : if (selector.empty())
385 : : {
386 : 4 : selector = "SELECT DISTINCT ";
387 : 4 : selector += tpkey + " FROM " TRANSACTION_TABLE;
388 : : }
389 : 4 : gnc_sql_slots_load_for_sql_subquery (sql_be, selector,
390 : : (BookLookupFn)xaccTransLookup);
391 : 4 : }
392 : :
393 : : // Commit all of the transactions, but don't scrub because any
394 : : // scrubbing changes won't be written back to the database
395 : 6 : xaccDisableDataScrubbing();
396 : 15 : for (auto instance : instances)
397 : 9 : xaccTransCommitEdit(GNC_TRANSACTION(instance));
398 : 6 : xaccEnableDataScrubbing();
399 : 18 : }
400 : :
401 : :
402 : : /* ================================================================= */
403 : : /**
404 : : * Creates the transaction and split tables.
405 : : *
406 : : * @param sql_be SQL backend
407 : : */
408 : : void
409 : 10 : GncSqlTransBackend::create_tables (GncSqlBackend* sql_be)
410 : : {
411 : : gint version;
412 : : gboolean ok;
413 : :
414 : 10 : g_return_if_fail (sql_be != NULL);
415 : :
416 : 10 : version = sql_be->get_table_version( m_table_name.c_str());
417 : 10 : if (version == 0)
418 : : {
419 : 5 : (void)sql_be->create_table(TRANSACTION_TABLE, TX_TABLE_VERSION,
420 : : tx_col_table);
421 : 5 : ok = sql_be->create_index ("tx_post_date_index", TRANSACTION_TABLE,
422 : : post_date_col_table);
423 : 5 : if (!ok)
424 : : {
425 : 0 : PERR ("Unable to create index\n");
426 : : }
427 : : }
428 : 5 : else if (version < m_version)
429 : : {
430 : : /* Upgrade:
431 : : 1->2: 64 bit int handling
432 : : 2->3: allow dates to be NULL
433 : : 3->4: Use DATETIME instead of TIMESTAMP in MySQL
434 : : */
435 : 0 : sql_be->upgrade_table(m_table_name.c_str(), tx_col_table);
436 : 0 : sql_be->set_table_version (m_table_name.c_str(), m_version);
437 : 0 : PINFO ("Transactions table upgraded from version %d to version %d\n",
438 : : version, m_version);
439 : : }
440 : : }
441 : : void
442 : 10 : GncSqlSplitBackend::create_tables (GncSqlBackend* sql_be)
443 : : {
444 : 10 : g_return_if_fail (sql_be != nullptr);
445 : :
446 : 10 : auto version = sql_be->get_table_version( m_table_name.c_str());
447 : 10 : if (version == 0)
448 : : {
449 : 5 : (void)sql_be->create_table(m_table_name.c_str(),
450 : 5 : m_version, m_col_table);
451 : 5 : if (!sql_be->create_index("splits_tx_guid_index",
452 : : m_table_name.c_str(), tx_guid_col_table))
453 : 0 : PERR ("Unable to create index\n");
454 : 5 : if (!sql_be->create_index("splits_account_guid_index",
455 : : m_table_name.c_str(),
456 : : account_guid_col_table))
457 : 0 : PERR ("Unable to create index\n");
458 : : }
459 : 5 : else if (version < SPLIT_TABLE_VERSION)
460 : : {
461 : :
462 : : /* Upgrade:
463 : : 1->2: 64 bit int handling
464 : : 3->4: Split reconcile date can be NULL
465 : : 4->5: Use DATETIME instead of TIMESTAMP in MySQL
466 : : */
467 : 0 : sql_be->upgrade_table(m_table_name.c_str(), split_col_table);
468 : 0 : if (!sql_be->create_index("splits_tx_guid_index",
469 : : m_table_name.c_str(),
470 : : tx_guid_col_table))
471 : 0 : PERR ("Unable to create index\n");
472 : 0 : if (!sql_be->create_index("splits_account_guid_index",
473 : : m_table_name.c_str(),
474 : : account_guid_col_table))
475 : 0 : PERR ("Unable to create index\n");
476 : 0 : sql_be->set_table_version (m_table_name.c_str(), m_version);
477 : 0 : PINFO ("Splits table upgraded from version %d to version %d\n", version,
478 : : m_version);
479 : : }
480 : : }
481 : : /* ================================================================= */
482 : : /**
483 : : * Callback function to delete slots for a split
484 : : *
485 : : * @param data Split
486 : : * @param user_data split_info_t structure contain operation info
487 : : */
488 : : static void
489 : 0 : delete_split_slots_cb (gpointer data, gpointer user_data)
490 : : {
491 : 0 : split_info_t* split_info = (split_info_t*)user_data;
492 : 0 : Split* pSplit = GNC_SPLIT (data);
493 : :
494 : 0 : g_return_if_fail (data != NULL);
495 : 0 : g_return_if_fail (GNC_IS_SPLIT (data));
496 : 0 : g_return_if_fail (user_data != NULL);
497 : :
498 : 0 : if (split_info->is_ok)
499 : : {
500 : 0 : split_info->is_ok = gnc_sql_slots_delete (split_info->be,
501 : 0 : qof_instance_get_guid (QOF_INSTANCE (pSplit)));
502 : : }
503 : : }
504 : :
505 : : /**
506 : : * Deletes all of the splits for a transaction
507 : : *
508 : : * @param sql_be SQL backend
509 : : * @param pTx Transaction
510 : : * @return TRUE if successful, FALSE if unsuccessful
511 : : */
512 : : static gboolean
513 : 0 : delete_splits (GncSqlBackend* sql_be, Transaction* pTx)
514 : : {
515 : 0 : split_info_t split_info;
516 : :
517 : 0 : g_return_val_if_fail (sql_be != NULL, FALSE);
518 : 0 : g_return_val_if_fail (pTx != NULL, FALSE);
519 : :
520 : 0 : if (!sql_be->do_db_operation(OP_DB_DELETE, SPLIT_TABLE,
521 : : SPLIT_TABLE, pTx, tx_guid_col_table))
522 : : {
523 : 0 : return FALSE;
524 : : }
525 : 0 : split_info.be = sql_be;
526 : 0 : split_info.is_ok = TRUE;
527 : :
528 : 0 : g_list_foreach (xaccTransGetSplitList (pTx), delete_split_slots_cb,
529 : : &split_info);
530 : :
531 : 0 : return split_info.is_ok;
532 : : }
533 : :
534 : : /**
535 : : * Commits a split to the database
536 : : *
537 : : * @param sql_be SQL backend
538 : : * @param inst Split
539 : : * @return TRUE if successful, FALSE if error
540 : : */
541 : : bool
542 : 18 : GncSqlSplitBackend::commit (GncSqlBackend* sql_be, QofInstance* inst)
543 : : {
544 : : E_DB_OPERATION op;
545 : : gboolean is_infant;
546 : : gboolean is_ok;
547 : 18 : GncGUID* guid = (GncGUID*)qof_instance_get_guid (inst);
548 : :
549 : 18 : g_return_val_if_fail (inst != NULL, FALSE);
550 : 18 : g_return_val_if_fail (sql_be != NULL, FALSE);
551 : :
552 : 18 : is_infant = qof_instance_get_infant (inst);
553 : 18 : if (qof_instance_get_destroying (inst))
554 : : {
555 : 0 : op = OP_DB_DELETE;
556 : : }
557 : 18 : else if (sql_be->pristine() || is_infant)
558 : : {
559 : 18 : op = OP_DB_INSERT;
560 : : }
561 : : else
562 : : {
563 : 0 : op = OP_DB_UPDATE;
564 : : }
565 : :
566 : 18 : if (guid_equal (guid, guid_null ()))
567 : : {
568 : 0 : *guid = guid_new_return ();
569 : 0 : qof_instance_set_guid (inst, guid);
570 : : }
571 : :
572 : 18 : is_ok = sql_be->do_db_operation(op, SPLIT_TABLE, GNC_ID_SPLIT,
573 : : inst, split_col_table);
574 : :
575 : 18 : if (is_ok && !qof_instance_get_destroying (inst))
576 : : {
577 : 18 : is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst);
578 : : }
579 : :
580 : 18 : return is_ok;
581 : : }
582 : :
583 : :
584 : : bool
585 : 9 : GncSqlTransBackend::commit (GncSqlBackend* sql_be, QofInstance* inst)
586 : : {
587 : : E_DB_OPERATION op;
588 : 9 : gboolean is_ok = TRUE;
589 : 9 : const char* err = NULL;
590 : :
591 : 9 : g_return_val_if_fail (sql_be != NULL, FALSE);
592 : 9 : g_return_val_if_fail (inst != NULL, FALSE);
593 : :
594 : 9 : auto pTx = GNC_TRANS(inst);
595 : 9 : auto is_infant = qof_instance_get_infant (inst);
596 : 9 : if (qof_instance_get_destroying (inst))
597 : : {
598 : 0 : op = OP_DB_DELETE;
599 : : }
600 : 9 : else if (sql_be->pristine() || is_infant)
601 : : {
602 : 9 : op = OP_DB_INSERT;
603 : : }
604 : : else
605 : : {
606 : 0 : op = OP_DB_UPDATE;
607 : : }
608 : :
609 : 9 : if (op != OP_DB_DELETE)
610 : : {
611 : 9 : gnc_commodity* commodity = xaccTransGetCurrency (pTx);
612 : : // Ensure the commodity is in the db
613 : 9 : is_ok = sql_be->save_commodity(commodity);
614 : 9 : if (! is_ok)
615 : : {
616 : 0 : err = "Commodity save failed: Probably an invalid or missing currency";
617 : 0 : qof_backend_set_error ((QofBackend*)sql_be, ERR_BACKEND_DATA_CORRUPT);
618 : : }
619 : : }
620 : :
621 : 9 : if (is_ok)
622 : : {
623 : 9 : is_ok = sql_be->do_db_operation(op, TRANSACTION_TABLE, GNC_ID_TRANS,
624 : : pTx, tx_col_table);
625 : 9 : if (! is_ok)
626 : : {
627 : 0 : err = "Transaction header save failed. Check trace log for SQL errors";
628 : : }
629 : : }
630 : :
631 : 9 : if (is_ok)
632 : : {
633 : : // Commit slots
634 : 9 : auto guid = qof_instance_get_guid (inst);
635 : 9 : if (!qof_instance_get_destroying (inst))
636 : : {
637 : 9 : is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst);
638 : 9 : if (! is_ok)
639 : : {
640 : 0 : err = "Slots save failed. Check trace log for SQL errors";
641 : : }
642 : : }
643 : : else
644 : : {
645 : 0 : is_ok = gnc_sql_slots_delete (sql_be, guid);
646 : 0 : if (! is_ok)
647 : : {
648 : 0 : err = "Slots delete failed. Check trace log for SQL errors";
649 : : }
650 : 0 : if (is_ok)
651 : : {
652 : 0 : is_ok = delete_splits (sql_be, pTx);
653 : 0 : if (! is_ok)
654 : : {
655 : 0 : err = "Split delete failed. Check trace log for SQL errors";
656 : : }
657 : : }
658 : : }
659 : : }
660 : 9 : if (! is_ok)
661 : : {
662 : 0 : Split* split = xaccTransGetSplit (pTx, 0);
663 : 0 : Account* acc = xaccSplitGetAccount (split);
664 : 0 : gchar *datestr = qof_print_date (xaccTransGetDate (pTx));
665 : : /* FIXME: This needs to be implemented
666 : : const char *message1 = "Transaction %s dated %s in account %s not saved due to %s.%s";
667 : : const char *message2 = "\nDatabase may be corrupted, check your data carefully.";
668 : : qof_error_format_secondary_text( GTK_MESSAGE_DIALOG( msg ),
669 : : message1,
670 : : xaccTransGetDescription( pTx ),
671 : : qof_print_date( xaccTransGetDate( pTx ) ),
672 : : xaccAccountGetName( acc ),
673 : : err,
674 : : message2 );
675 : : */
676 : 0 : PERR ("Transaction %s dated %s in account %s not saved due to %s.\n",
677 : : xaccTransGetDescription (pTx), datestr, xaccAccountGetName (acc), err);
678 : 0 : g_free (datestr);
679 : : }
680 : 9 : return is_ok;
681 : : }
682 : :
683 : : /* ================================================================= */
684 : : /**
685 : : * Loads all transactions for an account.
686 : : *
687 : : * @param sql_be SQL backend
688 : : * @param account Account
689 : : */
690 : 3 : void gnc_sql_transaction_load_tx_for_account (GncSqlBackend* sql_be,
691 : : Account* account)
692 : : {
693 : : const GncGUID* guid;
694 : :
695 : 3 : g_return_if_fail (sql_be != NULL);
696 : 3 : g_return_if_fail (account != NULL);
697 : :
698 : 3 : guid = qof_instance_get_guid (QOF_INSTANCE (account));
699 : :
700 : 3 : const std::string tpkey(tx_col_table[0]->name()); //guid
701 : 3 : const std::string spkey(split_col_table[0]->name()); //guid
702 : 3 : const std::string stkey(split_col_table[1]->name()); //txn_guid
703 : 3 : const std::string sakey(split_col_table[2]->name()); //account_guid
704 : 3 : std::string sql("(SELECT DISTINCT ");
705 : 3 : sql += stkey + " FROM " SPLIT_TABLE " WHERE " + sakey + " = '";
706 : 3 : sql += gnc::GUID(*guid).to_string() + "')";
707 : 3 : query_transactions (sql_be, sql);
708 : 3 : }
709 : :
710 : : /**
711 : : * Loads all transactions. This might be used during a save-as operation to ensure that
712 : : * all data is in memory and ready to be saved.
713 : : *
714 : : * @param sql_be SQL backend
715 : : */
716 : : void
717 : 6 : GncSqlTransBackend::load_all (GncSqlBackend* sql_be)
718 : : {
719 : 6 : g_return_if_fail (sql_be != NULL);
720 : :
721 : 6 : auto root = gnc_book_get_root_account (sql_be->book());
722 : 6 : gnc_account_foreach_descendant(root, (AccountCb)xaccAccountBeginEdit,
723 : : nullptr);
724 : 6 : query_transactions (sql_be, "");
725 : 6 : gnc_account_foreach_descendant(root, (AccountCb)xaccAccountCommitEdit,
726 : : nullptr);
727 : : }
728 : :
729 : : typedef struct
730 : : {
731 : : GncSqlStatementPtr stmt;
732 : : gboolean has_been_run;
733 : : } split_query_info_t;
734 : :
735 : : /* ----------------------------------------------------------------- */
736 : : typedef struct
737 : : {
738 : : const GncSqlBackend* sql_be;
739 : : Account* acct;
740 : : char reconcile_state;
741 : : gnc_numeric balance;
742 : : } single_acct_balance_t;
743 : :
744 : : static void
745 : 0 : set_acct_bal_account_from_guid (gpointer pObject, gpointer pValue)
746 : : {
747 : 0 : single_acct_balance_t* bal = (single_acct_balance_t*)pObject;
748 : 0 : const GncGUID* guid = (const GncGUID*)pValue;
749 : :
750 : 0 : g_return_if_fail (pObject != NULL);
751 : 0 : g_return_if_fail (pValue != NULL);
752 : :
753 : 0 : bal->acct = xaccAccountLookup (guid, bal->sql_be->book());
754 : : }
755 : :
756 : : static void
757 : 0 : set_acct_bal_reconcile_state (gpointer pObject, gpointer pValue)
758 : : {
759 : 0 : single_acct_balance_t* bal = (single_acct_balance_t*)pObject;
760 : 0 : const gchar* s = (const gchar*)pValue;
761 : :
762 : 0 : g_return_if_fail (pObject != NULL);
763 : 0 : g_return_if_fail (pValue != NULL);
764 : :
765 : 0 : bal->reconcile_state = s[0];
766 : : }
767 : :
768 : : static void
769 : 0 : set_acct_bal_balance (gpointer pObject, gnc_numeric value)
770 : : {
771 : 0 : single_acct_balance_t* bal = (single_acct_balance_t*)pObject;
772 : :
773 : 0 : g_return_if_fail (pObject != NULL);
774 : :
775 : 0 : bal->balance = value;
776 : : }
777 : :
778 : : static const EntryVec acct_balances_col_table
779 : : {
780 : : gnc_sql_make_table_entry<CT_GUID>("account_guid", 0, 0, nullptr,
781 : : (QofSetterFunc)set_acct_bal_account_from_guid),
782 : : gnc_sql_make_table_entry<CT_STRING>("reconcile_state", 1, 0, nullptr,
783 : : (QofSetterFunc)set_acct_bal_reconcile_state),
784 : : gnc_sql_make_table_entry<CT_NUMERIC>("quantity", 0, 0, nullptr,
785 : : (QofSetterFunc)set_acct_bal_balance),
786 : : };
787 : :
788 : : /* ----------------------------------------------------------------- */
789 : : template<> void
790 : 19 : GncSqlColumnTableEntryImpl<CT_TXREF>::load (const GncSqlBackend* sql_be,
791 : : GncSqlRow& row,
792 : : QofIdTypeConst obj_name,
793 : : gpointer pObject) const noexcept
794 : : {
795 : 19 : g_return_if_fail (sql_be != NULL);
796 : 19 : g_return_if_fail (pObject != NULL);
797 : :
798 : 19 : auto val = row.get_string_at_col (m_col_name);
799 : 19 : if (!val)
800 : 0 : return;
801 : :
802 : : GncGUID guid;
803 : 19 : Transaction *tx = nullptr;
804 : 19 : if (string_to_guid (val->c_str(), &guid))
805 : 18 : tx = xaccTransLookup (&guid, sql_be->book());
806 : :
807 : : // If the transaction is not found, try loading it
808 : 19 : std::string tpkey(tx_col_table[0]->name());
809 : 19 : if (tx == nullptr)
810 : : {
811 : 2 : std::string sql = tpkey + " = '" + *val + "'";
812 : 1 : query_transactions ((GncSqlBackend*)sql_be, sql);
813 : 1 : tx = xaccTransLookup (&guid, sql_be->book());
814 : 1 : }
815 : :
816 : 19 : if (tx != nullptr)
817 : 18 : set_parameter (pObject, tx, get_setter(obj_name), m_gobj_param_name);
818 : :
819 : 19 : }
820 : :
821 : : template<> void
822 : 10 : GncSqlColumnTableEntryImpl<CT_TXREF>::add_to_table(ColVec& vec) const noexcept
823 : : {
824 : 10 : add_objectref_guid_to_table(vec);
825 : 10 : }
826 : :
827 : : template<> void
828 : 19 : GncSqlColumnTableEntryImpl<CT_TXREF>::add_to_query(QofIdTypeConst obj_name,
829 : : const gpointer pObject,
830 : : PairVec& vec) const noexcept
831 : : {
832 : 19 : add_objectref_guid_to_query(obj_name, pObject, vec);
833 : 19 : }
834 : :
835 : : /* ========================== END OF FILE ===================== */
|