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 1 : 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 15 : GncSqlTransBackend::GncSqlTransBackend() :
143 30 : GncSqlObjectBackend(TX_TABLE_VERSION, GNC_ID_TRANS,
144 75 : TRANSACTION_TABLE, tx_col_table) {}
145 :
146 15 : GncSqlSplitBackend::GncSqlSplitBackend() :
147 30 : GncSqlObjectBackend(SPLIT_TABLE_VERSION, GNC_ID_SPLIT,
148 75 : 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 22 : get_split_reconcile_state (gpointer pObject)
161 : {
162 : static gchar c[2];
163 :
164 22 : g_return_val_if_fail (pObject != NULL, NULL);
165 22 : g_return_val_if_fail (GNC_IS_SPLIT (pObject), NULL);
166 :
167 22 : c[0] = xaccSplitGetReconcile (GNC_SPLIT (pObject));
168 22 : c[1] = '\0';
169 22 : return (gpointer)c;
170 : }
171 :
172 : static void
173 23 : set_split_reconcile_state (gpointer pObject, gpointer pValue)
174 : {
175 23 : const gchar* s = (const gchar*)pValue;
176 :
177 23 : g_return_if_fail (pObject != NULL);
178 23 : g_return_if_fail (GNC_IS_SPLIT (pObject));
179 23 : g_return_if_fail (pValue != NULL);
180 :
181 23 : 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 23 : load_single_split (GncSqlBackend* sql_be, GncSqlRow& row)
204 : {
205 : const GncGUID* guid;
206 : GncGUID split_guid;
207 23 : Split* pSplit = NULL;
208 :
209 23 : g_return_val_if_fail (sql_be != NULL, NULL);
210 :
211 23 : guid = gnc_sql_load_guid (sql_be, row);
212 23 : if (guid == NULL) return NULL;
213 23 : 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 23 : split_guid = *guid;
221 23 : pSplit = xaccSplitLookup (&split_guid, sql_be->book());
222 : }
223 :
224 23 : if (pSplit)
225 0 : return pSplit; //Already loaded, nothing to do.
226 :
227 23 : pSplit = xaccMallocSplit (sql_be->book());
228 23 : gnc_sql_load_object (sql_be, row, GNC_ID_SPLIT, pSplit, split_col_table);
229 :
230 : /*# -ifempty */
231 23 : 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 23 : 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 23 : return pSplit;
246 : }
247 : static void
248 6 : load_splits_for_transactions (GncSqlBackend* sql_be, std::string selector)
249 : {
250 6 : g_return_if_fail (sql_be != NULL);
251 :
252 12 : const std::string spkey(split_col_table[0]->name());
253 12 : const std::string sskey(tx_guid_col_table[0]->name());
254 12 : const std::string tpkey(tx_col_table[0]->name());
255 :
256 6 : std::string sql("SELECT ");
257 6 : if (selector.empty())
258 : {
259 : sql += SPLIT_TABLE ".* FROM " SPLIT_TABLE " INNER JOIN "
260 12 : TRANSACTION_TABLE " ON " SPLIT_TABLE "." + sskey + " = "
261 6 : TRANSACTION_TABLE "." + tpkey;
262 6 : 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 6 : auto stmt = sql_be->create_statement_from_sql(sql);
269 6 : auto result = sql_be->execute_select_statement (stmt);
270 :
271 29 : for (auto row : *result)
272 29 : load_single_split (sql_be, row);
273 6 : sql = "SELECT DISTINCT ";
274 6 : sql += spkey + " FROM " SPLIT_TABLE " WHERE " + sskey + " IN " + selector;
275 6 : gnc_sql_slots_load_for_sql_subquery(sql_be, sql,
276 : (BookLookupFn)xaccSplitLookup);
277 6 : }
278 :
279 : static Transaction*
280 14 : load_single_tx (GncSqlBackend* sql_be, GncSqlRow& row)
281 : {
282 : const GncGUID* guid;
283 : GncGUID tx_guid;
284 : Transaction* pTx;
285 :
286 14 : g_return_val_if_fail (sql_be != NULL, NULL);
287 :
288 14 : guid = gnc_sql_load_guid (sql_be, row);
289 14 : if (guid == NULL) return NULL;
290 14 : tx_guid = *guid;
291 :
292 14 : pTx = xaccTransLookup (&tx_guid, sql_be->book());
293 14 : if (pTx)
294 2 : return nullptr; // Nothing to do.
295 :
296 12 : pTx = xaccMallocTransaction (sql_be->book());
297 12 : xaccTransBeginEdit (pTx);
298 12 : gnc_sql_load_object (sql_be, row, GNC_ID_TRANS, pTx, tx_col_table);
299 :
300 12 : 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 12 : 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 12 : query_transactions (GncSqlBackend* sql_be, std::string selector)
338 : {
339 16 : g_return_if_fail (sql_be != NULL);
340 :
341 24 : const std::string tpkey(tx_col_table[0]->name());
342 12 : std::string sql("SELECT * FROM " TRANSACTION_TABLE);
343 :
344 12 : if (!selector.empty() && selector[0] == '(')
345 3 : sql += " WHERE " + tpkey + " IN " + selector;
346 9 : else if (!selector.empty()) // plain condition
347 1 : sql += " WHERE " + selector;
348 12 : auto stmt = sql_be->create_statement_from_sql(sql);
349 12 : auto result = sql_be->execute_select_statement(stmt);
350 12 : 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 8 : InstanceVec instances;
360 8 : instances.reserve(result->size());
361 22 : for (auto row : *result)
362 : {
363 14 : tx = load_single_tx (sql_be, row);
364 14 : if (tx != nullptr)
365 : {
366 12 : xaccTransScrubPostedDate (tx);
367 12 : instances.push_back(QOF_INSTANCE(tx));
368 : }
369 22 : }
370 :
371 : // Load all splits and slots for the transactions
372 8 : if (!instances.empty())
373 : {
374 6 : const std::string tpkey(tx_col_table[0]->name());
375 6 : 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 6 : load_splits_for_transactions (sql_be, selector);
383 :
384 6 : if (selector.empty())
385 : {
386 6 : selector = "SELECT DISTINCT ";
387 6 : selector += tpkey + " FROM " TRANSACTION_TABLE;
388 : }
389 6 : gnc_sql_slots_load_for_sql_subquery (sql_be, selector,
390 : (BookLookupFn)xaccTransLookup);
391 6 : }
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 8 : xaccDisableDataScrubbing();
396 28 : for (auto instance : instances)
397 12 : xaccTransCommitEdit(GNC_TRANSACTION(instance));
398 8 : xaccEnableDataScrubbing();
399 20 : }
400 :
401 :
402 : /* ================================================================= */
403 : /**
404 : * Creates the transaction and split tables.
405 : *
406 : * @param sql_be SQL backend
407 : */
408 : void
409 13 : GncSqlTransBackend::create_tables (GncSqlBackend* sql_be)
410 : {
411 : gint version;
412 : gboolean ok;
413 :
414 13 : g_return_if_fail (sql_be != NULL);
415 :
416 26 : version = sql_be->get_table_version( m_table_name.c_str());
417 13 : if (version == 0)
418 : {
419 12 : (void)sql_be->create_table(TRANSACTION_TABLE, TX_TABLE_VERSION,
420 : tx_col_table);
421 24 : ok = sql_be->create_index ("tx_post_date_index", TRANSACTION_TABLE,
422 : post_date_col_table);
423 6 : if (!ok)
424 : {
425 0 : PERR ("Unable to create index\n");
426 : }
427 : }
428 7 : 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 13 : GncSqlSplitBackend::create_tables (GncSqlBackend* sql_be)
443 : {
444 13 : g_return_if_fail (sql_be != nullptr);
445 :
446 26 : auto version = sql_be->get_table_version( m_table_name.c_str());
447 13 : if (version == 0)
448 : {
449 6 : (void)sql_be->create_table(m_table_name.c_str(),
450 6 : m_version, m_col_table);
451 12 : if (!sql_be->create_index("splits_tx_guid_index",
452 18 : m_table_name.c_str(), tx_guid_col_table))
453 0 : PERR ("Unable to create index\n");
454 12 : if (!sql_be->create_index("splits_account_guid_index",
455 18 : m_table_name.c_str(),
456 : account_guid_col_table))
457 0 : PERR ("Unable to create index\n");
458 : }
459 7 : 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 0 : 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 0 : 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 1 : delete_split_slots_cb (gpointer data, gpointer user_data)
490 : {
491 1 : split_info_t* split_info = (split_info_t*)user_data;
492 1 : Split* pSplit = GNC_SPLIT (data);
493 :
494 1 : g_return_if_fail (data != NULL);
495 1 : g_return_if_fail (GNC_IS_SPLIT (data));
496 1 : g_return_if_fail (user_data != NULL);
497 :
498 1 : if (split_info->is_ok)
499 : {
500 1 : split_info->is_ok = gnc_sql_slots_delete (split_info->be,
501 1 : 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 1 : delete_splits (GncSqlBackend* sql_be, Transaction* pTx)
514 : {
515 1 : split_info_t split_info;
516 :
517 1 : g_return_val_if_fail (sql_be != NULL, FALSE);
518 1 : g_return_val_if_fail (pTx != NULL, FALSE);
519 :
520 1 : 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 1 : split_info.be = sql_be;
526 1 : split_info.is_ok = TRUE;
527 :
528 1 : g_list_foreach (xaccTransGetSplitList (pTx), delete_split_slots_cb,
529 : &split_info);
530 :
531 1 : 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 23 : GncSqlSplitBackend::commit (GncSqlBackend* sql_be, QofInstance* inst)
543 : {
544 : E_DB_OPERATION op;
545 : gboolean is_infant;
546 : gboolean is_ok;
547 23 : GncGUID* guid = (GncGUID*)qof_instance_get_guid (inst);
548 :
549 23 : g_return_val_if_fail (inst != NULL, FALSE);
550 23 : g_return_val_if_fail (sql_be != NULL, FALSE);
551 :
552 23 : is_infant = qof_instance_get_infant (inst);
553 23 : if (qof_instance_get_destroying (inst))
554 : {
555 1 : op = OP_DB_DELETE;
556 : }
557 22 : else if (sql_be->pristine() || is_infant)
558 : {
559 21 : op = OP_DB_INSERT;
560 : }
561 : else
562 : {
563 1 : op = OP_DB_UPDATE;
564 : }
565 :
566 23 : if (guid_equal (guid, guid_null ()))
567 : {
568 0 : *guid = guid_new_return ();
569 0 : qof_instance_set_guid (inst, guid);
570 : }
571 :
572 23 : is_ok = sql_be->do_db_operation(op, SPLIT_TABLE, GNC_ID_SPLIT,
573 : inst, split_col_table);
574 :
575 23 : if (is_ok && !qof_instance_get_destroying (inst))
576 : {
577 22 : is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst);
578 : }
579 :
580 23 : return is_ok;
581 : }
582 :
583 :
584 : bool
585 12 : GncSqlTransBackend::commit (GncSqlBackend* sql_be, QofInstance* inst)
586 : {
587 : E_DB_OPERATION op;
588 12 : gboolean is_ok = TRUE;
589 12 : const char* err = NULL;
590 :
591 12 : g_return_val_if_fail (sql_be != NULL, FALSE);
592 12 : g_return_val_if_fail (inst != NULL, FALSE);
593 :
594 12 : auto pTx = GNC_TRANS(inst);
595 12 : auto is_infant = qof_instance_get_infant (inst);
596 12 : if (qof_instance_get_destroying (inst))
597 : {
598 1 : op = OP_DB_DELETE;
599 : }
600 11 : else if (sql_be->pristine() || is_infant)
601 : {
602 11 : op = OP_DB_INSERT;
603 : }
604 : else
605 : {
606 0 : op = OP_DB_UPDATE;
607 : }
608 :
609 12 : if (op != OP_DB_DELETE)
610 : {
611 11 : gnc_commodity* commodity = xaccTransGetCurrency (pTx);
612 : // Ensure the commodity is in the db
613 11 : is_ok = sql_be->save_commodity(commodity);
614 11 : 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 12 : if (is_ok)
622 : {
623 12 : is_ok = sql_be->do_db_operation(op, TRANSACTION_TABLE, GNC_ID_TRANS,
624 : pTx, tx_col_table);
625 12 : if (! is_ok)
626 : {
627 0 : err = "Transaction header save failed. Check trace log for SQL errors";
628 : }
629 : }
630 :
631 12 : if (is_ok)
632 : {
633 : // Commit slots
634 12 : auto guid = qof_instance_get_guid (inst);
635 12 : if (!qof_instance_get_destroying (inst))
636 : {
637 11 : is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst);
638 11 : if (! is_ok)
639 : {
640 0 : err = "Slots save failed. Check trace log for SQL errors";
641 : }
642 : }
643 : else
644 : {
645 1 : is_ok = gnc_sql_slots_delete (sql_be, guid);
646 1 : if (! is_ok)
647 : {
648 0 : err = "Slots delete failed. Check trace log for SQL errors";
649 : }
650 1 : if (is_ok)
651 : {
652 1 : is_ok = delete_splits (sql_be, pTx);
653 1 : if (! is_ok)
654 : {
655 0 : err = "Split delete failed. Check trace log for SQL errors";
656 : }
657 : }
658 : }
659 : }
660 12 : 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 12 : 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 6 : const std::string tpkey(tx_col_table[0]->name()); //guid
701 6 : const std::string spkey(split_col_table[0]->name()); //guid
702 6 : const std::string stkey(split_col_table[1]->name()); //txn_guid
703 6 : 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 8 : GncSqlTransBackend::load_all (GncSqlBackend* sql_be)
718 : {
719 8 : g_return_if_fail (sql_be != NULL);
720 :
721 8 : auto root = gnc_book_get_root_account (sql_be->book());
722 8 : gnc_account_foreach_descendant(root, (AccountCb)xaccAccountBeginEdit,
723 : nullptr);
724 8 : query_transactions (sql_be, "");
725 8 : 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 24 : GncSqlColumnTableEntryImpl<CT_TXREF>::load (const GncSqlBackend* sql_be,
791 : GncSqlRow& row,
792 : QofIdTypeConst obj_name,
793 : gpointer pObject) const noexcept
794 : {
795 24 : g_return_if_fail (sql_be != NULL);
796 24 : g_return_if_fail (pObject != NULL);
797 :
798 24 : auto val = row.get_string_at_col (m_col_name);
799 24 : if (!val)
800 0 : return;
801 :
802 : GncGUID guid;
803 24 : Transaction *tx = nullptr;
804 24 : if (string_to_guid (val->c_str(), &guid))
805 23 : tx = xaccTransLookup (&guid, sql_be->book());
806 :
807 : // If the transaction is not found, try loading it
808 24 : std::string tpkey(tx_col_table[0]->name());
809 24 : if (tx == nullptr)
810 : {
811 1 : 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 24 : if (tx != nullptr)
817 23 : set_parameter (pObject, tx, get_setter(obj_name), m_gobj_param_name);
818 :
819 24 : }
820 :
821 : template<> void
822 12 : GncSqlColumnTableEntryImpl<CT_TXREF>::add_to_table(ColVec& vec) const noexcept
823 : {
824 12 : add_objectref_guid_to_table(vec);
825 12 : }
826 :
827 : template<> void
828 23 : GncSqlColumnTableEntryImpl<CT_TXREF>::add_to_query(QofIdTypeConst obj_name,
829 : const gpointer pObject,
830 : PairVec& vec) const noexcept
831 : {
832 23 : add_objectref_guid_to_query(obj_name, pObject, vec);
833 23 : }
834 :
835 : /* ========================== END OF FILE ===================== */
|