gnucash/libgnucash/backend/sql/gnc-transaction-sql.cpp
John Ralls 243bf8afce Bug 796369 - Notes lost or perhaps just not displaying when using...
SQLite backend.

Release Note: This bug caused data loss if you saved your SQLite3
database to a different file or database.

The problem is that in SQLite3 (though not in MySQL or PgSQL) the
subquery ((SELECT DISTINCT guid FROM transactions)) (note the double
parentheses) returns only the first guid in the subquery's results.

Some transactions are loaded by special queries and those queries are
also used to retrieve the transaction's slots so they weren't affected.
2018-05-25 12:58:39 -07:00

1001 lines
31 KiB
C++

/********************************************************************
* gnc-transaction-sql.c: load and save data to SQL *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
\********************************************************************/
/** @file gnc-transaction-sql.c
* @brief load and save data to SQL
* @author Copyright (c) 2006-2008 Phil Longstaff <plongstaff@rogers.com>
*
* This file implements the top-level QofBackend API for saving/
* restoring data to/from an SQL db
*/
#include <guid.hpp>
extern "C"
{
#include <config.h>
#include <glib/gi18n.h>
#include "qof.h"
#include "qofquery-p.h"
#include "qofquerycore-p.h"
#include "Account.h"
#include "Transaction.h"
#include <Scrub.h>
#include "gnc-lot.h"
#include "engine-helpers.h"
#include "gnc-commodity.h"
#include "gnc-engine.h"
#ifdef S_SPLINT_S
#include "splint-defs.h"
#endif
}
#include <string>
#include <sstream>
#include "escape.h"
#include <gnc-datetime.hpp>
#include "gnc-sql-connection.hpp"
#include "gnc-sql-backend.hpp"
#include "gnc-sql-object-backend.hpp"
#include "gnc-sql-column-table-entry.hpp"
#include "gnc-transaction-sql.h"
#include "gnc-commodity-sql.h"
#include "gnc-slots-sql.h"
#define SIMPLE_QUERY_COMPILATION 1
static QofLogModule log_module = G_LOG_DOMAIN;
#define TRANSACTION_TABLE "transactions"
#define TX_TABLE_VERSION 4
#define SPLIT_TABLE "splits"
#define SPLIT_TABLE_VERSION 4
struct split_info_t : public write_objects_t
{
split_info_t () = default;
split_info_t (GncSqlBackend* sql_be, bool o,
GncSqlObjectBackend* e, const GncGUID* g):
write_objects_t(sql_be, o, e), guid{g} {}
const GncGUID* guid;
};
#define TX_MAX_NUM_LEN 2048
#define TX_MAX_DESCRIPTION_LEN 2048
static const EntryVec tx_col_table
{
gnc_sql_make_table_entry<CT_GUID>("guid", 0, COL_NNUL | COL_PKEY, "guid"),
gnc_sql_make_table_entry<CT_COMMODITYREF>("currency_guid", 0, COL_NNUL,
"currency"),
gnc_sql_make_table_entry<CT_STRING>("num", TX_MAX_NUM_LEN, COL_NNUL, "num"),
gnc_sql_make_table_entry<CT_TIMESPEC>("post_date", 0, 0, "post-date"),
gnc_sql_make_table_entry<CT_TIMESPEC>("enter_date", 0, 0, "enter-date"),
gnc_sql_make_table_entry<CT_STRING>("description", TX_MAX_DESCRIPTION_LEN,
0, "description"),
};
static gpointer get_split_reconcile_state (gpointer pObject);
static void set_split_reconcile_state (gpointer pObject, gpointer pValue);
static void set_split_lot (gpointer pObject, gpointer pLot);
#define SPLIT_MAX_MEMO_LEN 2048
#define SPLIT_MAX_ACTION_LEN 2048
static const EntryVec split_col_table
{
gnc_sql_make_table_entry<CT_GUID>("guid", 0, COL_NNUL | COL_PKEY, "guid"),
gnc_sql_make_table_entry<CT_TXREF>("tx_guid", 0, COL_NNUL, "transaction"),
gnc_sql_make_table_entry<CT_ACCOUNTREF>("account_guid", 0, COL_NNUL,
"account"),
gnc_sql_make_table_entry<CT_STRING>("memo", SPLIT_MAX_MEMO_LEN, COL_NNUL,
"memo"),
gnc_sql_make_table_entry<CT_STRING>("action", SPLIT_MAX_ACTION_LEN,
COL_NNUL, "action"),
gnc_sql_make_table_entry<CT_STRING>("reconcile_state", 1, COL_NNUL,
(QofAccessFunc)get_split_reconcile_state,
set_split_reconcile_state),
gnc_sql_make_table_entry<CT_TIMESPEC>("reconcile_date", 0, 0,
"reconcile-date"),
gnc_sql_make_table_entry<CT_NUMERIC>("value", 0, COL_NNUL, "value"),
gnc_sql_make_table_entry<CT_NUMERIC>("quantity", 0, COL_NNUL, "amount"),
gnc_sql_make_table_entry<CT_LOTREF>("lot_guid", 0, 0,
(QofAccessFunc)xaccSplitGetLot,
set_split_lot),
};
static const EntryVec post_date_col_table
{
gnc_sql_make_table_entry<CT_TIMESPEC>("post_date", 0, 0, "post-date"),
};
static const EntryVec account_guid_col_table
{
gnc_sql_make_table_entry<CT_ACCOUNTREF>("account_guid", 0, COL_NNUL,
"account"),
};
static const EntryVec tx_guid_col_table
{
gnc_sql_make_table_entry<CT_GUID>("tx_guid", 0, 0, "guid"),
};
GncSqlTransBackend::GncSqlTransBackend() :
GncSqlObjectBackend(TX_TABLE_VERSION, GNC_ID_TRANS,
TRANSACTION_TABLE, tx_col_table) {}
GncSqlSplitBackend::GncSqlSplitBackend() :
GncSqlObjectBackend(SPLIT_TABLE_VERSION, GNC_ID_SPLIT,
SPLIT_TABLE, split_col_table) {}
/* These functions exist but have not been tested.
#if LOAD_TRANSACTIONS_AS_NEEDED
compile_split_query,
run_split_query,
free_split_query,
*/
/* ================================================================= */
static gpointer
get_split_reconcile_state (gpointer pObject)
{
static gchar c[2];
g_return_val_if_fail (pObject != NULL, NULL);
g_return_val_if_fail (GNC_IS_SPLIT (pObject), NULL);
c[0] = xaccSplitGetReconcile (GNC_SPLIT (pObject));
c[1] = '\0';
return (gpointer)c;
}
static void
set_split_reconcile_state (gpointer pObject, gpointer pValue)
{
const gchar* s = (const gchar*)pValue;
g_return_if_fail (pObject != NULL);
g_return_if_fail (GNC_IS_SPLIT (pObject));
g_return_if_fail (pValue != NULL);
xaccSplitSetReconcile (GNC_SPLIT (pObject), s[0]);
}
static void
set_split_lot (gpointer pObject, gpointer pLot)
{
GNCLot* lot;
Split* split;
g_return_if_fail (pObject != NULL);
g_return_if_fail (GNC_IS_SPLIT (pObject));
if (pLot == NULL) return;
g_return_if_fail (GNC_IS_LOT (pLot));
split = GNC_SPLIT (pObject);
lot = GNC_LOT (pLot);
gnc_lot_add_split (lot, split);
}
static Split*
load_single_split (GncSqlBackend* sql_be, GncSqlRow& row)
{
const GncGUID* guid;
GncGUID split_guid;
Split* pSplit = NULL;
gboolean bad_guid = FALSE;
g_return_val_if_fail (sql_be != NULL, NULL);
guid = gnc_sql_load_guid (sql_be, row);
if (guid == NULL) return NULL;
if (guid_equal (guid, guid_null ()))
{
PWARN ("Bad GUID, creating new");
bad_guid = TRUE;
split_guid = guid_new_return ();
}
else
{
split_guid = *guid;
pSplit = xaccSplitLookup (&split_guid, sql_be->book());
}
if (pSplit == NULL)
{
pSplit = xaccMallocSplit (sql_be->book());
}
/* If the split is dirty, don't overwrite it */
if (!qof_instance_is_dirty (QOF_INSTANCE (pSplit)))
{
gnc_sql_load_object (sql_be, row, GNC_ID_SPLIT, pSplit, split_col_table);
}
/*# -ifempty */
if (pSplit != xaccSplitLookup (&split_guid, sql_be->book()))
{
gchar guidstr[GUID_ENCODING_LENGTH + 1];
guid_to_string_buff (qof_instance_get_guid (pSplit), guidstr);
PERR ("A malformed split with id %s was found in the dataset.", guidstr);
qof_backend_set_error ((QofBackend*)sql_be, ERR_BACKEND_DATA_CORRUPT);
pSplit = NULL;
}
return pSplit;
}
static void
load_splits_for_transactions (GncSqlBackend* sql_be, std::string selector)
{
g_return_if_fail (sql_be != NULL);
const std::string spkey(split_col_table[0]->name());
const std::string sskey(tx_guid_col_table[0]->name());
const std::string tpkey(tx_col_table[0]->name());
std::string sql("SELECT ");
if (selector.empty())
{
sql += SPLIT_TABLE ".* FROM " SPLIT_TABLE " INNER JOIN "
TRANSACTION_TABLE " ON " SPLIT_TABLE "." + sskey + " = "
TRANSACTION_TABLE "." + tpkey;
selector = "(SELECT DISTINCT " + tpkey + " FROM " TRANSACTION_TABLE ")";
}
else
sql += " * FROM " SPLIT_TABLE " WHERE " + sskey + " IN " + selector;
// Execute the query and load the splits
auto stmt = sql_be->create_statement_from_sql(sql);
auto result = sql_be->execute_select_statement (stmt);
for (auto row : *result)
Split* s = load_single_split (sql_be, row);
sql = "SELECT DISTINCT ";
sql += spkey + " FROM " SPLIT_TABLE " WHERE " + sskey + " IN " + selector;
gnc_sql_slots_load_for_sql_subquery(sql_be, sql,
(BookLookupFn)xaccSplitLookup);
}
static Transaction*
load_single_tx (GncSqlBackend* sql_be, GncSqlRow& row)
{
const GncGUID* guid;
GncGUID tx_guid;
Transaction* pTx;
g_return_val_if_fail (sql_be != NULL, NULL);
guid = gnc_sql_load_guid (sql_be, row);
if (guid == NULL) return NULL;
tx_guid = *guid;
// Don't overwrite the transaction if it's already been loaded (and possibly modified).
// However increase the edit level, it may be modified while loading its splits
pTx = xaccTransLookup (&tx_guid, sql_be->book());
if (pTx != NULL)
{
xaccTransBeginEdit (pTx);
return NULL;
}
pTx = xaccMallocTransaction (sql_be->book());
xaccTransBeginEdit (pTx);
gnc_sql_load_object (sql_be, row, GNC_ID_TRANS, pTx, tx_col_table);
if (pTx != xaccTransLookup (&tx_guid, sql_be->book()))
{
gchar guidstr[GUID_ENCODING_LENGTH + 1];
guid_to_string_buff (qof_instance_get_guid (pTx), guidstr);
PERR ("A malformed transaction with id %s was found in the dataset.", guidstr);
qof_backend_set_error ((QofBackend*)sql_be, ERR_BACKEND_DATA_CORRUPT);
pTx = NULL;
}
return pTx;
}
/**
* Structure to hold start/end balances for each account. The values are
* saved before splits are loaded, and then used to adjust the start balances
* so that the end balances (which are calculated and correct on initial load)
* are unchanged.
*/
typedef struct
{
Account* acc;
gnc_numeric start_bal;
gnc_numeric end_bal;
gnc_numeric start_cleared_bal;
gnc_numeric end_cleared_bal;
gnc_numeric start_reconciled_bal;
gnc_numeric end_reconciled_bal;
} full_acct_balances_t;
/**
* Executes a transaction query statement and loads the transactions and all
* of the splits.
*
* @param sql_be SQL backend
* @param stmt SQL statement
*/
static void
query_transactions (GncSqlBackend* sql_be, std::string selector)
{
g_return_if_fail (sql_be != NULL);
const std::string tpkey(tx_col_table[0]->name());
std::string sql("SELECT * FROM " TRANSACTION_TABLE);
if (!selector.empty() && selector[0] == '(')
sql += " WHERE " + tpkey + " IN " + selector;
else if (!selector.empty()) // plain condition
sql += " WHERE " + selector;
auto stmt = sql_be->create_statement_from_sql(sql);
auto result = sql_be->execute_select_statement(stmt);
if (result->begin() == result->end())
{
PINFO("Query %s returned no results", sql.c_str());
return;
}
Transaction* tx;
// Load the transactions
InstanceVec instances;
instances.reserve(result->size());
for (auto row : *result)
{
tx = load_single_tx (sql_be, row);
if (tx != nullptr)
{
xaccTransScrubPostedDate (tx);
instances.push_back(QOF_INSTANCE(tx));
}
}
// Load all splits and slots for the transactions
if (!instances.empty())
{
const std::string tpkey(tx_col_table[0]->name());
if (!selector.empty() && (selector[0] != '('))
{
auto tselector = std::string ("(SELECT DISTINCT ");
tselector += tpkey + " FROM " TRANSACTION_TABLE " WHERE " + selector + ")";
selector = tselector;
}
load_splits_for_transactions (sql_be, selector);
if (selector.empty())
{
selector = "SELECT DISTINCT ";
selector += tpkey + " FROM " TRANSACTION_TABLE;
}
gnc_sql_slots_load_for_sql_subquery (sql_be, selector,
(BookLookupFn)xaccTransLookup);
}
// Commit all of the transactions
for (auto instance : instances)
xaccTransCommitEdit(GNC_TRANSACTION(instance));
}
/* ================================================================= */
/**
* Creates the transaction and split tables.
*
* @param sql_be SQL backend
*/
void
GncSqlTransBackend::create_tables (GncSqlBackend* sql_be)
{
gint version;
gboolean ok;
g_return_if_fail (sql_be != NULL);
version = sql_be->get_table_version( m_table_name.c_str());
if (version == 0)
{
(void)sql_be->create_table(TRANSACTION_TABLE, TX_TABLE_VERSION,
tx_col_table);
ok = sql_be->create_index ("tx_post_date_index", TRANSACTION_TABLE,
post_date_col_table);
if (!ok)
{
PERR ("Unable to create index\n");
}
}
else if (version < m_version)
{
/* Upgrade:
1->2: 64 bit int handling
2->3: allow dates to be NULL
3->4: Use DATETIME instead of TIMESTAMP in MySQL
*/
sql_be->upgrade_table(m_table_name.c_str(), tx_col_table);
sql_be->set_table_version (m_table_name.c_str(), m_version);
PINFO ("Transactions table upgraded from version %d to version %d\n",
version, m_version);
}
}
void
GncSqlSplitBackend::create_tables (GncSqlBackend* sql_be)
{
g_return_if_fail (sql_be != nullptr);
auto version = sql_be->get_table_version( m_table_name.c_str());
if (version == 0)
{
(void)sql_be->create_table(m_table_name.c_str(),
m_version, m_col_table);
if (!sql_be->create_index("splits_tx_guid_index",
m_table_name.c_str(), tx_guid_col_table))
PERR ("Unable to create index\n");
if (!sql_be->create_index("splits_account_guid_index",
m_table_name.c_str(),
account_guid_col_table))
PERR ("Unable to create index\n");
}
else if (version < SPLIT_TABLE_VERSION)
{
/* Upgrade:
1->2: 64 bit int handling
3->4: Split reconcile date can be NULL */
sql_be->upgrade_table(m_table_name.c_str(), split_col_table);
if (!sql_be->create_index("splits_tx_guid_index",
m_table_name.c_str(),
tx_guid_col_table))
PERR ("Unable to create index\n");
if (!sql_be->create_index("splits_account_guid_index",
m_table_name.c_str(),
account_guid_col_table))
PERR ("Unable to create index\n");
sql_be->set_table_version (m_table_name.c_str(), m_version);
PINFO ("Splits table upgraded from version %d to version %d\n", version,
m_version);
}
}
/* ================================================================= */
/**
* Callback function to delete slots for a split
*
* @param data Split
* @param user_data split_info_t structure contain operation info
*/
static void
delete_split_slots_cb (gpointer data, gpointer user_data)
{
split_info_t* split_info = (split_info_t*)user_data;
Split* pSplit = GNC_SPLIT (data);
g_return_if_fail (data != NULL);
g_return_if_fail (GNC_IS_SPLIT (data));
g_return_if_fail (user_data != NULL);
if (split_info->is_ok)
{
split_info->is_ok = gnc_sql_slots_delete (split_info->be,
qof_instance_get_guid (QOF_INSTANCE (pSplit)));
}
}
/**
* Deletes all of the splits for a transaction
*
* @param sql_be SQL backend
* @param pTx Transaction
* @return TRUE if successful, FALSE if unsuccessful
*/
static gboolean
delete_splits (GncSqlBackend* sql_be, Transaction* pTx)
{
split_info_t split_info;
g_return_val_if_fail (sql_be != NULL, FALSE);
g_return_val_if_fail (pTx != NULL, FALSE);
if (!sql_be->do_db_operation(OP_DB_DELETE, SPLIT_TABLE,
SPLIT_TABLE, pTx, tx_guid_col_table))
{
return FALSE;
}
split_info.be = sql_be;
split_info.is_ok = TRUE;
g_list_foreach (xaccTransGetSplitList (pTx), delete_split_slots_cb,
&split_info);
return split_info.is_ok;
}
/**
* Commits a split to the database
*
* @param sql_be SQL backend
* @param inst Split
* @return TRUE if successful, FALSE if error
*/
bool
GncSqlSplitBackend::commit (GncSqlBackend* sql_be, QofInstance* inst)
{
E_DB_OPERATION op;
gboolean is_infant;
gboolean is_ok;
GncGUID* guid = (GncGUID*)qof_instance_get_guid (inst);
g_return_val_if_fail (inst != NULL, FALSE);
g_return_val_if_fail (sql_be != NULL, FALSE);
is_infant = qof_instance_get_infant (inst);
if (qof_instance_get_destroying (inst))
{
op = OP_DB_DELETE;
}
else if (sql_be->pristine() || is_infant)
{
op = OP_DB_INSERT;
}
else
{
op = OP_DB_UPDATE;
}
if (guid_equal (guid, guid_null ()))
{
*guid = guid_new_return ();
qof_instance_set_guid (inst, guid);
}
is_ok = sql_be->do_db_operation(op, SPLIT_TABLE, GNC_ID_SPLIT,
inst, split_col_table);
if (is_ok && !qof_instance_get_destroying (inst))
{
is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst);
}
return is_ok;
}
bool
GncSqlTransBackend::commit (GncSqlBackend* sql_be, QofInstance* inst)
{
E_DB_OPERATION op;
gboolean is_ok = TRUE;
const char* err = NULL;
g_return_val_if_fail (sql_be != NULL, FALSE);
g_return_val_if_fail (inst != NULL, FALSE);
auto pTx = GNC_TRANS(inst);
auto is_infant = qof_instance_get_infant (inst);
if (qof_instance_get_destroying (inst))
{
op = OP_DB_DELETE;
}
else if (sql_be->pristine() || is_infant)
{
op = OP_DB_INSERT;
}
else
{
op = OP_DB_UPDATE;
}
if (op != OP_DB_DELETE)
{
gnc_commodity* commodity = xaccTransGetCurrency (pTx);
// Ensure the commodity is in the db
is_ok = sql_be->save_commodity(commodity);
if (! is_ok)
{
err = "Commodity save failed: Probably an invalid or missing currency";
qof_backend_set_error ((QofBackend*)sql_be, ERR_BACKEND_DATA_CORRUPT);
}
}
if (is_ok)
{
is_ok = sql_be->do_db_operation(op, TRANSACTION_TABLE, GNC_ID_TRANS,
pTx, tx_col_table);
if (! is_ok)
{
err = "Transaction header save failed. Check trace log for SQL errors";
}
}
if (is_ok)
{
// Commit slots
auto guid = qof_instance_get_guid (inst);
if (!qof_instance_get_destroying (inst))
{
is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst);
if (! is_ok)
{
err = "Slots save failed. Check trace log for SQL errors";
}
}
else
{
is_ok = gnc_sql_slots_delete (sql_be, guid);
if (! is_ok)
{
err = "Slots delete failed. Check trace log for SQL errors";
}
if (is_ok)
{
is_ok = delete_splits (sql_be, pTx);
if (! is_ok)
{
err = "Split delete failed. Check trace log for SQL errors";
}
}
}
}
if (! is_ok)
{
Split* split = xaccTransGetSplit (pTx, 0);
Account* acc = xaccSplitGetAccount (split);
/* FIXME: This needs to be implemented
const char *message1 = "Transaction %s dated %s in account %s not saved due to %s.%s";
const char *message2 = "\nDatabase may be corrupted, check your data carefully.";
qof_error_format_secondary_text( GTK_MESSAGE_DIALOG( msg ),
message1,
xaccTransGetDescription( pTx ),
qof_print_date( xaccTransGetDate( pTx ) ),
xaccAccountGetName( acc ),
err,
message2 );
*/
PERR ("Transaction %s dated %s in account %s not saved due to %s.\n",
xaccTransGetDescription (pTx),
qof_print_date (xaccTransGetDate (pTx)),
xaccAccountGetName (acc),
err);
}
return is_ok;
}
/* ================================================================= */
/**
* Loads all transactions for an account.
*
* @param sql_be SQL backend
* @param account Account
*/
void gnc_sql_transaction_load_tx_for_account (GncSqlBackend* sql_be,
Account* account)
{
const GncGUID* guid;
gchar guid_buf[GUID_ENCODING_LENGTH + 1];
gchar* query_sql;
g_return_if_fail (sql_be != NULL);
g_return_if_fail (account != NULL);
guid = qof_instance_get_guid (QOF_INSTANCE (account));
const std::string tpkey(tx_col_table[0]->name()); //guid
const std::string spkey(split_col_table[0]->name()); //guid
const std::string stkey(split_col_table[1]->name()); //txn_guid
const std::string sakey(split_col_table[2]->name()); //account_guid
std::string sql("(SELECT DISTINCT ");
sql += stkey + " FROM " SPLIT_TABLE " WHERE " + sakey + " = '";
sql += gnc::GUID(*guid).to_string() + "')";
query_transactions (sql_be, sql);
}
/**
* Loads all transactions. This might be used during a save-as operation to ensure that
* all data is in memory and ready to be saved.
*
* @param sql_be SQL backend
*/
void
GncSqlTransBackend::load_all (GncSqlBackend* sql_be)
{
g_return_if_fail (sql_be != NULL);
query_transactions (sql_be, "");
}
static void
convert_query_comparison_to_sql (QofQueryPredData* pPredData,
gboolean isInverted, std::stringstream& sql)
{
if (pPredData->how == QOF_COMPARE_LT
|| (isInverted && pPredData->how == QOF_COMPARE_GTE))
sql << "<";
else if (pPredData->how == QOF_COMPARE_LTE
|| (isInverted && pPredData->how == QOF_COMPARE_GT))
sql << "<=";
else if (pPredData->how == QOF_COMPARE_EQUAL
|| (isInverted && pPredData->how == QOF_COMPARE_NEQ))
sql << "=";
else if (pPredData->how == QOF_COMPARE_GT
|| (isInverted && pPredData->how == QOF_COMPARE_LTE))
sql << ">";
else if (pPredData->how == QOF_COMPARE_GTE
|| (isInverted && pPredData->how == QOF_COMPARE_LT))
sql << ">=";
else if (pPredData->how == QOF_COMPARE_NEQ
|| (isInverted && pPredData->how == QOF_COMPARE_EQUAL))
sql << "~=";
else
{
PERR ("Unknown comparison type\n");
sql << "??";
}
}
static void
convert_query_term_to_sql (const GncSqlBackend* sql_be, const gchar* fieldName,
QofQueryTerm* pTerm, std::stringstream& sql)
{
QofQueryPredData* pPredData;
gboolean isInverted;
g_return_if_fail (pTerm != NULL);
pPredData = qof_query_term_get_pred_data (pTerm);
isInverted = qof_query_term_is_inverted (pTerm);
if (g_strcmp0 (pPredData->type_name, QOF_TYPE_GUID) == 0)
{
query_guid_t guid_data = (query_guid_t)pPredData;
GList* guid_entry;
sql << "(" << fieldName;
switch (guid_data->options)
{
case QOF_GUID_MATCH_ANY:
sql << (isInverted ? " NOT IN (" : " IN (");
break;
case QOF_GUID_MATCH_NONE:
sql << (isInverted ? " IN (" : " NOT IN (");
break;
default:
PERR ("Unexpected GncGUID match type: %d\n", guid_data->options);
}
for (guid_entry = guid_data->guids; guid_entry != NULL;
guid_entry = guid_entry->next)
{
gchar guid_buf[GUID_ENCODING_LENGTH + 1];
if (guid_entry != guid_data->guids) sql << ",";
(void)guid_to_string_buff (static_cast<GncGUID*> (guid_entry->data),
guid_buf);
sql << guid_buf;
}
sql << "))";
}
else if (g_strcmp0 (pPredData->type_name, QOF_TYPE_CHAR) == 0)
{
query_char_t char_data = (query_char_t)pPredData;
int i;
if (isInverted) sql << "NOT(";
if (char_data->options == QOF_CHAR_MATCH_NONE) sql << "NOT ";
sql << "(";
for (i = 0; char_data->char_list[i] != '\0'; i++)
{
if (i != 0) sql << " OR ";
sql << fieldName << " = '" << char_data->char_list[i] << "'";
}
sql << ") ";
if (isInverted) sql << ") ";
}
else if (g_strcmp0 (pPredData->type_name, QOF_TYPE_STRING) == 0)
{
query_string_t string_data = (query_string_t)pPredData;
sqlEscape* escape = sqlEscape_new ();
if (isInverted || pPredData->how == QOF_COMPARE_NEQ)
sql << "NOT(";
sql << fieldName;
if (string_data->is_regex ||
string_data->options == QOF_STRING_MATCH_CASEINSENSITIVE)
{
PWARN ("String is_regex || option = QOF_STRING_MATCH_INSENSITIVE\n");
}
// sql << " ~" ;
// } else {
sql << " =";
// }
// if( string_data->options == QOF_STRING_MATCH_CASEINSENSITIVE ) {
// sql+= "*";
// }
sql << "'" << sqlEscapeString (escape, string_data->matchstring) << "'";
if (pPredData->how == QOF_COMPARE_NEQ) sql << ")";
if (isInverted) sql << ")";
sqlEscape_destroy (escape);
}
else
{
sql << "(" << fieldName;
convert_query_comparison_to_sql (pPredData, isInverted, sql);
if (strcmp (pPredData->type_name, QOF_TYPE_NUMERIC) == 0)
{
query_numeric_t pData = (query_numeric_t)pPredData;
sql << gnc_numeric_to_double (pData->amount);
}
else if (g_strcmp0 (pPredData->type_name, QOF_TYPE_DATE) == 0)
{
query_date_t date_data = (query_date_t)pPredData;
GncDateTime time(date_data->date);
sql << time.format_zulu ("%Y-%m-%d %H:%M:%S");
}
else if (strcmp (pPredData->type_name, QOF_TYPE_INT32) == 0)
{
query_int32_t pData = (query_int32_t)pPredData;
sql << pData->val;
}
else if (strcmp (pPredData->type_name, QOF_TYPE_INT64) == 0)
{
query_int64_t pData = (query_int64_t)pPredData;
sql << pData->val;
}
else if (strcmp (pPredData->type_name, QOF_TYPE_DOUBLE) == 0)
{
query_double_t pData = (query_double_t)pPredData;
sql << pData->val;
}
else if (strcmp (pPredData->type_name, QOF_TYPE_BOOLEAN) == 0)
{
query_boolean_t pData = (query_boolean_t)pPredData;
sql << pData->val;
}
else
{
PERR ("Unknown query predicate type: %s\n", pPredData->type_name);
}
sql << ")";
}
}
typedef struct
{
GncSqlStatementPtr stmt;
gboolean has_been_run;
} split_query_info_t;
/* ----------------------------------------------------------------- */
typedef struct
{
const GncSqlBackend* sql_be;
Account* acct;
char reconcile_state;
gnc_numeric balance;
} single_acct_balance_t;
static void
set_acct_bal_account_from_guid (gpointer pObject, gpointer pValue)
{
single_acct_balance_t* bal = (single_acct_balance_t*)pObject;
const GncGUID* guid = (const GncGUID*)pValue;
g_return_if_fail (pObject != NULL);
g_return_if_fail (pValue != NULL);
bal->acct = xaccAccountLookup (guid, bal->sql_be->book());
}
static void
set_acct_bal_reconcile_state (gpointer pObject, gpointer pValue)
{
single_acct_balance_t* bal = (single_acct_balance_t*)pObject;
const gchar* s = (const gchar*)pValue;
g_return_if_fail (pObject != NULL);
g_return_if_fail (pValue != NULL);
bal->reconcile_state = s[0];
}
static void
set_acct_bal_balance (gpointer pObject, gnc_numeric value)
{
single_acct_balance_t* bal = (single_acct_balance_t*)pObject;
g_return_if_fail (pObject != NULL);
bal->balance = value;
}
static const EntryVec acct_balances_col_table
{
gnc_sql_make_table_entry<CT_GUID>("account_guid", 0, 0, nullptr,
(QofSetterFunc)set_acct_bal_account_from_guid),
gnc_sql_make_table_entry<CT_STRING>("reconcile_state", 1, 0, nullptr,
(QofSetterFunc)set_acct_bal_reconcile_state),
gnc_sql_make_table_entry<CT_NUMERIC>("quantity", 0, 0, nullptr,
(QofSetterFunc)set_acct_bal_balance),
};
/* ----------------------------------------------------------------- */
template<> void
GncSqlColumnTableEntryImpl<CT_TXREF>::load (const GncSqlBackend* sql_be,
GncSqlRow& row,
QofIdTypeConst obj_name,
gpointer pObject) const noexcept
{
const gchar* guid_str;
g_return_if_fail (sql_be != NULL);
g_return_if_fail (pObject != NULL);
try
{
auto val = row.get_string_at_col (m_col_name);
GncGUID guid;
Transaction *tx = nullptr;
if (string_to_guid (val.c_str(), &guid))
tx = xaccTransLookup (&guid, sql_be->book());
// If the transaction is not found, try loading it
std::string tpkey(tx_col_table[0]->name());
if (tx == nullptr)
{
std::string sql = tpkey + " = '" + val + "'";
query_transactions ((GncSqlBackend*)sql_be, sql);
tx = xaccTransLookup (&guid, sql_be->book());
}
if (tx != nullptr)
set_parameter (pObject, tx, get_setter(obj_name), m_gobj_param_name);
}
catch (std::invalid_argument&) {}
}
template<> void
GncSqlColumnTableEntryImpl<CT_TXREF>::add_to_table(ColVec& vec) const noexcept
{
add_objectref_guid_to_table(vec);
}
template<> void
GncSqlColumnTableEntryImpl<CT_TXREF>::add_to_query(QofIdTypeConst obj_name,
const gpointer pObject,
PairVec& vec) const noexcept
{
add_objectref_guid_to_query(obj_name, pObject, vec);
}
/* ========================== END OF FILE ===================== */