31#include <glib/gi18n.h>
34#include "qofquery-p.h"
35#include "qofquerycore-p.h"
39#include <TransactionP.hpp>
42#include "engine-helpers.h"
47#include "splint-defs.h"
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"
64#define SIMPLE_QUERY_COMPILATION 1
66static QofLogModule log_module = G_LOG_DOMAIN;
68#define TRANSACTION_TABLE "transactions"
69#define TX_TABLE_VERSION 4
70#define SPLIT_TABLE "splits"
71#define SPLIT_TABLE_VERSION 5
82#define TX_MAX_NUM_LEN 2048
83#define TX_MAX_DESCRIPTION_LEN 2048
85static const EntryVec tx_col_table
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,
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,
97static gpointer get_split_reconcile_state (gpointer pObject);
98static void set_split_reconcile_state (gpointer pObject, gpointer pValue);
99static void set_split_lot (gpointer pObject, gpointer pLot);
101#define SPLIT_MAX_MEMO_LEN 2048
102#define SPLIT_MAX_ACTION_LEN 2048
104static const EntryVec split_col_table
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,
110 gnc_sql_make_table_entry<CT_STRING>(
"memo", SPLIT_MAX_MEMO_LEN, COL_NNUL,
112 gnc_sql_make_table_entry<CT_STRING>(
"action", SPLIT_MAX_ACTION_LEN,
114 gnc_sql_make_table_entry<CT_STRING>(
"reconcile_state", 1, COL_NNUL,
116 set_split_reconcile_state),
117 gnc_sql_make_table_entry<CT_TIME>(
"reconcile_date", 0, 0,
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,
126static const EntryVec post_date_col_table
128 gnc_sql_make_table_entry<CT_TIME>(
"post_date", 0, 0,
"post-date"),
131static const EntryVec account_guid_col_table
133 gnc_sql_make_table_entry<CT_ACCOUNTREF>(
"account_guid", 0, COL_NNUL,
137static const EntryVec tx_guid_col_table
139 gnc_sql_make_table_entry<CT_GUID>(
"tx_guid", 0, 0,
"guid"),
142GncSqlTransBackend::GncSqlTransBackend() :
144 TRANSACTION_TABLE, tx_col_table) {}
146GncSqlSplitBackend::GncSqlSplitBackend() :
148 SPLIT_TABLE, split_col_table) {}
160get_split_reconcile_state (gpointer pObject)
164 g_return_val_if_fail (pObject != NULL, NULL);
165 g_return_val_if_fail (GNC_IS_SPLIT (pObject), NULL);
173set_split_reconcile_state (gpointer pObject, gpointer pValue)
175 const gchar* s = (
const gchar*)pValue;
177 g_return_if_fail (pObject != NULL);
178 g_return_if_fail (GNC_IS_SPLIT (pObject));
179 g_return_if_fail (pValue != NULL);
185set_split_lot (gpointer pObject, gpointer pLot)
190 g_return_if_fail (pObject != NULL);
191 g_return_if_fail (GNC_IS_SPLIT (pObject));
193 if (pLot == NULL)
return;
195 g_return_if_fail (GNC_IS_LOT (pLot));
197 split = GNC_SPLIT (pObject);
198 lot = GNC_LOT (pLot);
207 Split* pSplit = NULL;
209 g_return_val_if_fail (sql_be != NULL, NULL);
211 guid = gnc_sql_load_guid (sql_be, row);
212 if (guid == NULL)
return NULL;
215 PWARN (
"Bad GUID, creating new");
228 gnc_sql_load_object (sql_be, row, GNC_ID_SPLIT, pSplit, split_col_table);
235 PERR (
"A malformed split with id %s was found in the dataset.", guidstr);
243 PERR(
"Split %s created with no account!", guidstr);
248load_splits_for_transactions (
GncSqlBackend* sql_be, std::string selector)
250 g_return_if_fail (sql_be != NULL);
252 const std::string spkey(split_col_table[0]->name());
253 const std::string sskey(tx_guid_col_table[0]->name());
254 const std::string tpkey(tx_col_table[0]->name());
256 std::string sql(
"SELECT ");
257 if (selector.empty())
259 sql += SPLIT_TABLE
".* FROM " SPLIT_TABLE
" INNER JOIN "
260 TRANSACTION_TABLE
" ON " SPLIT_TABLE
"." + sskey +
" = "
261 TRANSACTION_TABLE
"." + tpkey;
262 selector =
"(SELECT DISTINCT " + tpkey +
" FROM " TRANSACTION_TABLE
")";
265 sql +=
" * FROM " SPLIT_TABLE
" WHERE " + sskey +
" IN " + selector;
268 auto stmt = sql_be->create_statement_from_sql(sql);
271 for (
auto row : *result)
272 load_single_split (sql_be, row);
273 sql =
"SELECT DISTINCT ";
274 sql += spkey +
" FROM " SPLIT_TABLE
" WHERE " + sskey +
" IN " + selector;
275 gnc_sql_slots_load_for_sql_subquery(sql_be, sql,
286 g_return_val_if_fail (sql_be != NULL, NULL);
288 guid = gnc_sql_load_guid (sql_be, row);
289 if (guid == NULL)
return NULL;
298 gnc_sql_load_object (sql_be, row, GNC_ID_TRANS, pTx, tx_col_table);
304 PERR (
"A malformed transaction with id %s was found in the dataset.", guidstr);
321 gnc_numeric start_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;
337query_transactions (
GncSqlBackend* sql_be, std::string selector)
339 g_return_if_fail (sql_be != NULL);
341 const std::string tpkey(tx_col_table[0]->name());
342 std::string sql(
"SELECT * FROM " TRANSACTION_TABLE);
344 if (!selector.empty() && selector[0] ==
'(')
345 sql +=
" WHERE " + tpkey +
" IN " + selector;
346 else if (!selector.empty())
347 sql +=
" WHERE " + selector;
348 auto stmt = sql_be->create_statement_from_sql(sql);
350 if (result->begin() == result->end())
352 PINFO(
"Query %s returned no results", sql.c_str());
359 InstanceVec instances;
360 instances.reserve(result->size());
361 for (
auto row : *result)
363 tx = load_single_tx (sql_be, row);
367 instances.push_back(QOF_INSTANCE(tx));
372 if (!instances.empty())
374 const std::string tpkey(tx_col_table[0]->name());
375 if (!selector.empty() && (selector[0] !=
'('))
377 auto tselector = std::string (
"(SELECT DISTINCT ");
378 tselector += tpkey +
" FROM " TRANSACTION_TABLE
" WHERE " + selector +
")";
379 selector = tselector;
382 load_splits_for_transactions (sql_be, selector);
384 if (selector.empty())
386 selector =
"SELECT DISTINCT ";
387 selector += tpkey +
" FROM " TRANSACTION_TABLE;
389 gnc_sql_slots_load_for_sql_subquery (sql_be, selector,
395 xaccDisableDataScrubbing();
396 for (
auto instance : instances)
398 xaccEnableDataScrubbing();
414 g_return_if_fail (sql_be != NULL);
419 (void)sql_be->
create_table(TRANSACTION_TABLE, TX_TABLE_VERSION,
421 ok = sql_be->
create_index (
"tx_post_date_index", TRANSACTION_TABLE,
422 post_date_col_table);
425 PERR (
"Unable to create index\n");
428 else if (version < m_version)
437 PINFO (
"Transactions table upgraded from version %d to version %d\n",
444 g_return_if_fail (sql_be !=
nullptr);
452 m_table_name.c_str(), tx_guid_col_table))
453 PERR (
"Unable to create index\n");
455 m_table_name.c_str(),
456 account_guid_col_table))
457 PERR (
"Unable to create index\n");
459 else if (version < SPLIT_TABLE_VERSION)
467 sql_be->
upgrade_table(m_table_name.c_str(), split_col_table);
469 m_table_name.c_str(),
471 PERR (
"Unable to create index\n");
473 m_table_name.c_str(),
474 account_guid_col_table))
475 PERR (
"Unable to create index\n");
477 PINFO (
"Splits table upgraded from version %d to version %d\n", version,
489delete_split_slots_cb (gpointer data, gpointer user_data)
492 Split* pSplit = GNC_SPLIT (data);
494 g_return_if_fail (data != NULL);
495 g_return_if_fail (GNC_IS_SPLIT (data));
496 g_return_if_fail (user_data != NULL);
498 if (split_info->is_ok)
500 split_info->is_ok = gnc_sql_slots_delete (split_info->be,
517 g_return_val_if_fail (sql_be != NULL, FALSE);
518 g_return_val_if_fail (pTx != NULL, FALSE);
521 SPLIT_TABLE, pTx, tx_guid_col_table))
525 split_info.be = sql_be;
526 split_info.is_ok = TRUE;
531 return split_info.is_ok;
549 g_return_val_if_fail (inst != NULL, FALSE);
550 g_return_val_if_fail (sql_be != NULL, FALSE);
552 is_infant = qof_instance_get_infant (inst);
557 else if (sql_be->pristine() || is_infant)
569 qof_instance_set_guid (inst, guid);
573 inst, split_col_table);
577 is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst);
588 gboolean is_ok = TRUE;
589 const char* err = NULL;
591 g_return_val_if_fail (sql_be != NULL, FALSE);
592 g_return_val_if_fail (inst != NULL, FALSE);
594 auto pTx = GNC_TRANS(inst);
595 auto is_infant = qof_instance_get_infant (inst);
600 else if (sql_be->pristine() || is_infant)
609 if (op != OP_DB_DELETE)
616 err =
"Commodity save failed: Probably an invalid or missing currency";
627 err =
"Transaction header save failed. Check trace log for SQL errors";
637 is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst);
640 err =
"Slots save failed. Check trace log for SQL errors";
645 is_ok = gnc_sql_slots_delete (sql_be, guid);
648 err =
"Slots delete failed. Check trace log for SQL errors";
652 is_ok = delete_splits (sql_be, pTx);
655 err =
"Split delete failed. Check trace log for SQL errors";
676 PERR (
"Transaction %s dated %s in account %s not saved due to %s.\n",
695 g_return_if_fail (sql_be != NULL);
696 g_return_if_fail (account != NULL);
700 const std::string tpkey(tx_col_table[0]->name());
701 const std::string spkey(split_col_table[0]->name());
702 const std::string stkey(split_col_table[1]->name());
703 const std::string sakey(split_col_table[2]->name());
704 std::string sql(
"(SELECT DISTINCT ");
705 sql += stkey +
" FROM " SPLIT_TABLE
" WHERE " + sakey +
" = '";
706 sql +=
gnc::GUID(*guid).to_string() +
"')";
707 query_transactions (sql_be, sql);
719 g_return_if_fail (sql_be != NULL);
721 auto root = gnc_book_get_root_account (sql_be->book());
724 query_transactions (sql_be,
"");
731 GncSqlStatementPtr stmt;
732 gboolean has_been_run;
740 char reconcile_state;
745set_acct_bal_account_from_guid (gpointer pObject, gpointer pValue)
750 g_return_if_fail (pObject != NULL);
751 g_return_if_fail (pValue != NULL);
757set_acct_bal_reconcile_state (gpointer pObject, gpointer pValue)
760 const gchar* s = (
const gchar*)pValue;
762 g_return_if_fail (pObject != NULL);
763 g_return_if_fail (pValue != NULL);
765 bal->reconcile_state = s[0];
769set_acct_bal_balance (gpointer pObject, gnc_numeric value)
773 g_return_if_fail (pObject != NULL);
775 bal->balance = value;
778static const EntryVec acct_balances_col_table
780 gnc_sql_make_table_entry<CT_GUID>(
"account_guid", 0, 0,
nullptr,
782 gnc_sql_make_table_entry<CT_STRING>(
"reconcile_state", 1, 0,
nullptr,
784 gnc_sql_make_table_entry<CT_NUMERIC>(
"quantity", 0, 0,
nullptr,
793 gpointer pObject)
const noexcept
795 g_return_if_fail (sql_be != NULL);
796 g_return_if_fail (pObject != NULL);
798 auto val = row.get_string_at_col (m_col_name);
803 Transaction *tx =
nullptr;
804 if (string_to_guid (val->c_str(), &guid))
808 std::string tpkey(tx_col_table[0]->name());
811 std::string sql = tpkey +
" = '" + *val +
"'";
817 set_parameter (pObject, tx, get_setter(obj_name), m_gobj_param_name);
824 add_objectref_guid_to_table(vec);
829 const gpointer pObject,
830 PairVec& vec)
const noexcept
832 add_objectref_guid_to_query(obj_name, pObject, vec);
Account handling public routines.
convert single-entry accounts to clean double-entry
API for Transactions and Splits (journal entries)
Main SQL backend structure.
uint_t get_table_version(const std::string &table_name) const noexcept
Returns the version number for a DB table.
bool create_index(const std::string &index_name, const std::string &table_name, const EntryVec &col_table) const noexcept
Creates an index in 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.
void upgrade_table(const std::string &table_name, const EntryVec &col_table) noexcept
Upgrades a table to a new structure.
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 save_commodity(gnc_commodity *comm) noexcept
Ensure that a commodity referenced in another object is in fact saved in the database.
bool set_table_version(const std::string &table_name, uint_t version) noexcept
Registers the version for a table.
void add_to_query(QofIdTypeConst obj_name, void *pObject, PairVec &vec) const noexcept override
Add a pair of the table column heading and object's value's string representation to a PairVec; used ...
void add_to_table(ColVec &vec) const noexcept override
Add a GncSqlColumnInfo structure for the column type to a ColVec.
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.
Encapsulates per-class table schema with functions to load, create a table, commit a changed front-en...
const EntryVec & m_col_table
The front-end QofIdType.
Row of SQL Query results.
bool commit(GncSqlBackend *sql_be, QofInstance *inst) override
Commits a split to the database.
void create_tables(GncSqlBackend *) override
Conditionally create or update a database table from m_col_table.
bool commit(GncSqlBackend *sql_be, QofInstance *inst) override
UPDATE/INSERT a single instance of m_type_name into the database.
void create_tables(GncSqlBackend *) override
Creates the transaction and split tables.
void load_all(GncSqlBackend *) override
Loads all transactions.
load and save data to SQL
Commodity handling public routines.
All type declarations for the whole Gnucash engine.
load and save accounts data to SQL
load and save data to SQL
const char * xaccAccountGetName(const Account *acc)
Get the account's name.
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Account * xaccAccountLookup(const GncGUID *guid, QofBook *book)
The xaccAccountLookup() subroutine will return the account associated with the given id,...
void qof_backend_set_error(QofBackend *qof_be, QofBackendError err)
Set the error on the specified QofBackend.
@ ERR_BACKEND_DATA_CORRUPT
data in db is corrupt
gpointer(* QofAccessFunc)(gpointer object, const QofParam *param)
The QofAccessFunc defines an arbitrary function pointer for access functions.
void(* QofSetterFunc)(gpointer, gpointer)
The QofSetterFunc defines an function pointer for parameter setters.
char * qof_print_date(time64 t)
Convenience; calls through to qof_print_date_dmy_buff().
gnc_commodity * xaccTransGetCurrency(const Transaction *trans)
Returns the valuation commodity of this transaction.
void xaccTransCommitEdit(Transaction *trans)
The xaccTransCommitEdit() method indicates that the changes to the transaction and its splits are com...
const char * xaccTransGetDescription(const Transaction *trans)
Gets the transaction Description.
Transaction * xaccTransLookup(const GncGUID *guid, QofBook *book)
The xaccTransLookup() subroutine will return the transaction associated with the given id,...
Split * xaccTransGetSplit(const Transaction *trans, int i)
Return a pointer to the indexed split in this transaction's split list.
Transaction * xaccMallocTransaction(QofBook *book)
The xaccMallocTransaction() will malloc memory and initialize it.
SplitList * xaccTransGetSplitList(const Transaction *trans)
The xaccTransGetSplitList() method returns a GList of the splits in a transaction.
void xaccTransBeginEdit(Transaction *trans)
The xaccTransBeginEdit() method must be called before any changes are made to a transaction or any of...
time64 xaccTransGetDate(const Transaction *trans)
Retrieve the posted date of the transaction.
const gchar * QofIdTypeConst
QofIdTypeConst declaration.
const GncGUID * guid_null(void)
Returns a GncGUID which is guaranteed to never reference any entity.
#define GUID_ENCODING_LENGTH
Number of characters needed to encode a guid as a string not including the null terminator.
gboolean guid_equal(const GncGUID *guid_1, const GncGUID *guid_2)
Given two GUIDs, return TRUE if they are non-NULL and equal.
GncGUID guid_new_return(void)
Generate a new id.
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...
const GncGUID * qof_instance_get_guid(gconstpointer inst)
Return the GncGUID of this instance.
gboolean qof_instance_get_destroying(gconstpointer ptr)
Retrieve the flag that indicates whether or not this object is about to be destroyed.
#define PINFO(format, args...)
Print an informational note.
#define PERR(format, args...)
Log a serious error.
#define PWARN(format, args...)
Log a warning.
void gnc_lot_add_split(GNCLot *lot, Split *split)
Adds a split to this lot.
void xaccTransScrubPostedDate(Transaction *trans)
Changes Transaction date_posted timestamps from 00:00 local to 11:00 UTC.
void xaccSplitSetReconcile(Split *split, char recn)
Set the reconcile flag.
char xaccSplitGetReconcile(const Split *split)
Returns the value of the reconcile flag.
Split * xaccSplitLookup(const GncGUID *guid, QofBook *book)
The xaccSplitLookup() subroutine will return the split associated with the given id,...
Split * xaccMallocSplit(QofBook *book)
Constructor.
GNCLot * xaccSplitGetLot(const Split *split)
Returns the pointer to the debited/credited Lot where this split belongs to, or NULL if it doesn't be...
Account * xaccSplitGetAccount(const Split *split)
Returns the account of this split, which was set through xaccAccountInsertSplit().
The type used to store guids in C.
Structure to hold start/end balances for each account.
Data-passing struct for callbacks to qof_object_foreach() used in GncSqlObjectBackend::write().