mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
There are a very few left that need deeper study, but this gets rid of most of the noise. For the most part it's just getting rid of extra variables or removing an assignment that is always replaced later but before any reads of the variable. A few are discarded result variables.
1013 lines
29 KiB
C++
1013 lines
29 KiB
C++
/********************************************************************
|
|
* gnc-sql-backend.cpp: Implementation of GncSqlBackend *
|
|
* *
|
|
* Copyright 2016 John Ralls <jralls@ceridwen.us> *
|
|
* *
|
|
* 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 *
|
|
\********************************************************************/
|
|
extern "C"
|
|
{
|
|
#include <config.h>
|
|
#include <gnc-prefs.h>
|
|
#include <gnc-engine.h>
|
|
#include <gnc-commodity.h>
|
|
#include <SX-book.h>
|
|
#include <Recurrence.h>
|
|
#include <gncBillTerm.h>
|
|
#include <gncTaxTable.h>
|
|
#include <gncInvoice.h>
|
|
#include <gnc-pricedb.h>
|
|
}
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
|
|
#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-sql-result.hpp"
|
|
|
|
#include "gnc-account-sql.h"
|
|
#include "gnc-book-sql.h"
|
|
#include "gnc-budget-sql.h"
|
|
#include "gnc-commodity-sql.h"
|
|
#include "gnc-lots-sql.h"
|
|
#include "gnc-price-sql.h"
|
|
#include "gnc-recurrence-sql.h"
|
|
#include "gnc-schedxaction-sql.h"
|
|
#include "gnc-slots-sql.h"
|
|
#include "gnc-transaction-sql.h"
|
|
|
|
#include "gnc-bill-term-sql.h"
|
|
#include "gnc-customer-sql.h"
|
|
#include "gnc-employee-sql.h"
|
|
#include "gnc-entry-sql.h"
|
|
#include "gnc-invoice-sql.h"
|
|
#include "gnc-job-sql.h"
|
|
#include "gnc-order-sql.h"
|
|
#include "gnc-tax-table-sql.h"
|
|
#include "gnc-vendor-sql.h"
|
|
|
|
static QofLogModule log_module = G_LOG_DOMAIN;
|
|
#define VERSION_TABLE_NAME "versions"
|
|
#define MAX_TABLE_NAME_LEN 50
|
|
#define TABLE_COL_NAME "table_name"
|
|
#define VERSION_COL_NAME "table_version"
|
|
|
|
using StrVec = std::vector<std::string>;
|
|
|
|
static EntryVec version_table
|
|
{
|
|
gnc_sql_make_table_entry<CT_STRING>(
|
|
TABLE_COL_NAME, MAX_TABLE_NAME_LEN, COL_PKEY | COL_NNUL),
|
|
gnc_sql_make_table_entry<CT_INT>(VERSION_COL_NAME, 0, COL_NNUL)
|
|
};
|
|
|
|
GncSqlBackend::GncSqlBackend(GncSqlConnection *conn, QofBook* book) :
|
|
QofBackend {}, m_conn{conn}, m_book{book}, m_loading{false},
|
|
m_in_query{false}, m_is_pristine_db{false}
|
|
{
|
|
if (conn != nullptr)
|
|
connect (conn);
|
|
}
|
|
|
|
void
|
|
GncSqlBackend::connect(GncSqlConnection *conn) noexcept
|
|
{
|
|
if (m_conn != nullptr && m_conn != conn)
|
|
delete m_conn;
|
|
finalize_version_info();
|
|
m_conn = conn;
|
|
}
|
|
|
|
GncSqlStatementPtr
|
|
GncSqlBackend::create_statement_from_sql(const std::string& str) const noexcept
|
|
{
|
|
auto stmt = m_conn->create_statement_from_sql(str);
|
|
if (stmt == nullptr)
|
|
{
|
|
PERR ("SQL error: %s\n", str.c_str());
|
|
qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR);
|
|
}
|
|
return stmt;
|
|
}
|
|
|
|
GncSqlResultPtr
|
|
GncSqlBackend::execute_select_statement(const GncSqlStatementPtr& stmt) const noexcept
|
|
{
|
|
auto result = m_conn->execute_select_statement(stmt);
|
|
if (result == nullptr)
|
|
{
|
|
PERR ("SQL error: %s\n", stmt->to_sql());
|
|
qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int
|
|
GncSqlBackend::execute_nonselect_statement(const GncSqlStatementPtr& stmt) const noexcept
|
|
{
|
|
auto result = m_conn->execute_nonselect_statement(stmt);
|
|
if (result == -1)
|
|
{
|
|
PERR ("SQL error: %s\n", stmt->to_sql());
|
|
qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string
|
|
GncSqlBackend::quote_string(const std::string& str) const noexcept
|
|
{
|
|
return m_conn->quote_string(str);
|
|
}
|
|
|
|
bool
|
|
GncSqlBackend::create_table(const std::string& table_name,
|
|
const EntryVec& col_table) const noexcept
|
|
{
|
|
ColVec info_vec;
|
|
gboolean ok = FALSE;
|
|
|
|
for (auto const& table_row : col_table)
|
|
{
|
|
table_row->add_to_table (info_vec);
|
|
}
|
|
return m_conn->create_table (table_name, info_vec);
|
|
|
|
}
|
|
|
|
bool
|
|
GncSqlBackend::create_table(const std::string& table_name, int table_version,
|
|
const EntryVec& col_table) noexcept
|
|
{
|
|
if (create_table (table_name, col_table))
|
|
return set_table_version (table_name, table_version);
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
GncSqlBackend::create_index(const std::string& index_name,
|
|
const std::string& table_name,
|
|
const EntryVec& col_table) const noexcept
|
|
{
|
|
return m_conn->create_index(index_name, table_name, col_table);
|
|
}
|
|
|
|
bool
|
|
GncSqlBackend::add_columns_to_table(const std::string& table_name,
|
|
const EntryVec& col_table) const noexcept
|
|
{
|
|
ColVec info_vec;
|
|
|
|
for (auto const& table_row : col_table)
|
|
{
|
|
table_row->add_to_table (info_vec);
|
|
}
|
|
return m_conn->add_columns_to_table(table_name, info_vec);
|
|
}
|
|
|
|
void
|
|
GncSqlBackend::update_progress(double pct) const noexcept
|
|
{
|
|
if (m_percentage != nullptr)
|
|
(m_percentage) (nullptr, pct);
|
|
}
|
|
|
|
void
|
|
GncSqlBackend::finish_progress() const noexcept
|
|
{
|
|
if (m_percentage != nullptr)
|
|
(m_percentage) (nullptr, -1.0);
|
|
}
|
|
|
|
void
|
|
GncSqlBackend::create_tables() noexcept
|
|
{
|
|
for(auto entry : m_backend_registry)
|
|
{
|
|
update_progress(101.0);
|
|
std::get<1>(entry)->create_tables(this);
|
|
}
|
|
}
|
|
|
|
/* Main object load order */
|
|
static const StrVec fixed_load_order
|
|
{ GNC_ID_BOOK, GNC_ID_COMMODITY, GNC_ID_ACCOUNT, GNC_ID_LOT, GNC_ID_TRANS };
|
|
|
|
/* Order in which business objects need to be loaded */
|
|
static const StrVec business_fixed_load_order =
|
|
{ GNC_ID_BILLTERM, GNC_ID_TAXTABLE, GNC_ID_INVOICE };
|
|
|
|
void
|
|
GncSqlBackend::ObjectBackendRegistry::load_remaining(GncSqlBackend* sql_be)
|
|
{
|
|
|
|
auto num_types = m_registry.size();
|
|
auto num_done = fixed_load_order.size() + business_fixed_load_order.size();
|
|
|
|
for (auto entry : m_registry)
|
|
{
|
|
std::string type;
|
|
GncSqlObjectBackendPtr obe = nullptr;
|
|
std::tie(type, obe) = entry;
|
|
|
|
/* Don't need to load anything if it has already been loaded with
|
|
* the fixed order.
|
|
*/
|
|
if (std::find(fixed_load_order.begin(), fixed_load_order.end(),
|
|
type) != fixed_load_order.end()) continue;
|
|
if (std::find(business_fixed_load_order.begin(),
|
|
business_fixed_load_order.end(),
|
|
type) != business_fixed_load_order.end()) continue;
|
|
|
|
num_done++;
|
|
sql_be->update_progress(num_done * 100 / num_types);
|
|
obe->load_all (sql_be);
|
|
}
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
QofIdType searchObj;
|
|
gpointer pCompiledQuery;
|
|
} gnc_sql_query_info;
|
|
|
|
/* callback structure */
|
|
typedef struct
|
|
{
|
|
gboolean is_known;
|
|
gboolean is_ok;
|
|
GncSqlBackend* sql_be;
|
|
QofInstance* inst;
|
|
QofQuery* pQuery;
|
|
gpointer pCompiledQuery;
|
|
gnc_sql_query_info* pQueryInfo;
|
|
} sql_backend;
|
|
|
|
|
|
void
|
|
GncSqlBackend::load (QofBook* book, QofBackendLoadType loadType)
|
|
{
|
|
Account* root;
|
|
|
|
g_return_if_fail (book != NULL);
|
|
|
|
ENTER ("sql_be=%p, book=%p", this, book);
|
|
|
|
m_loading = TRUE;
|
|
|
|
if (loadType == LOAD_TYPE_INITIAL_LOAD)
|
|
{
|
|
assert (m_book == nullptr);
|
|
m_book = book;
|
|
|
|
auto num_types = m_backend_registry.size();
|
|
auto num_done = 0;
|
|
|
|
/* Load any initial stuff. Some of this needs to happen in a certain order */
|
|
for (auto type : fixed_load_order)
|
|
{
|
|
num_done++;
|
|
auto obe = m_backend_registry.get_object_backend(type);
|
|
if (obe)
|
|
{
|
|
update_progress(num_done * 100 / num_types);
|
|
obe->load_all(this);
|
|
}
|
|
}
|
|
for (auto type : business_fixed_load_order)
|
|
{
|
|
num_done++;
|
|
auto obe = m_backend_registry.get_object_backend(type);
|
|
if (obe)
|
|
{
|
|
update_progress(num_done * 100 / num_types);
|
|
obe->load_all(this);
|
|
}
|
|
}
|
|
|
|
root = gnc_book_get_root_account( book );
|
|
gnc_account_foreach_descendant(root, (AccountCb)xaccAccountBeginEdit,
|
|
nullptr);
|
|
|
|
m_backend_registry.load_remaining(this);
|
|
|
|
gnc_account_foreach_descendant(root, (AccountCb)xaccAccountCommitEdit,
|
|
nullptr);
|
|
}
|
|
else if (loadType == LOAD_TYPE_LOAD_ALL)
|
|
{
|
|
// Load all transactions
|
|
auto obe = m_backend_registry.get_object_backend (GNC_ID_TRANS);
|
|
obe->load_all (this);
|
|
}
|
|
|
|
m_loading = FALSE;
|
|
std::for_each(m_postload_commodities.begin(), m_postload_commodities.end(),
|
|
[](gnc_commodity* comm) {
|
|
gnc_commodity_begin_edit(comm);
|
|
gnc_commodity_commit_edit(comm);
|
|
});
|
|
m_postload_commodities.empty();
|
|
|
|
/* Mark the sessoion as clean -- though it should never be marked
|
|
* dirty with this backend
|
|
*/
|
|
qof_book_mark_session_saved (book);
|
|
finish_progress();
|
|
|
|
LEAVE ("");
|
|
}
|
|
|
|
/* ================================================================= */
|
|
|
|
bool
|
|
GncSqlBackend::write_account_tree(Account* root)
|
|
{
|
|
GList* descendants;
|
|
GList* node;
|
|
bool is_ok = true;
|
|
|
|
g_return_val_if_fail (root != nullptr, false);
|
|
|
|
auto obe = m_backend_registry.get_object_backend(GNC_ID_ACCOUNT);
|
|
is_ok = obe->commit (this, QOF_INSTANCE (root));
|
|
if (is_ok)
|
|
{
|
|
descendants = gnc_account_get_descendants (root);
|
|
for (node = descendants; node != NULL && is_ok; node = g_list_next (node))
|
|
{
|
|
is_ok = obe->commit(this, QOF_INSTANCE (GNC_ACCOUNT (node->data)));
|
|
if (!is_ok) break;
|
|
}
|
|
g_list_free (descendants);
|
|
}
|
|
update_progress(101.0);
|
|
|
|
return is_ok;
|
|
}
|
|
|
|
bool
|
|
GncSqlBackend::write_accounts()
|
|
{
|
|
update_progress(101.0);
|
|
auto is_ok = write_account_tree (gnc_book_get_root_account (m_book));
|
|
if (is_ok)
|
|
{
|
|
update_progress(101.0);
|
|
is_ok = write_account_tree (gnc_book_get_template_root(m_book));
|
|
}
|
|
|
|
return is_ok;
|
|
}
|
|
|
|
static gboolean // Can't be bool because of signature for xaccAccountTreeForEach
|
|
write_tx (Transaction* tx, gpointer data)
|
|
{
|
|
auto s = static_cast<write_objects_t*>(data);
|
|
|
|
g_return_val_if_fail (tx != NULL, 0);
|
|
g_return_val_if_fail (data != NULL, 0);
|
|
|
|
s->commit (QOF_INSTANCE (tx));
|
|
auto splitbe = s->be->get_object_backend(GNC_ID_SPLIT);
|
|
for (auto split_node = xaccTransGetSplitList (tx);
|
|
split_node != nullptr && s->is_ok;
|
|
split_node = g_list_next (split_node))
|
|
{
|
|
s->is_ok = splitbe->commit(s->be, QOF_INSTANCE(split_node->data));
|
|
}
|
|
s->be->update_progress (101.0);
|
|
return (s->is_ok ? 0 : 1);
|
|
}
|
|
|
|
bool
|
|
GncSqlBackend::write_transactions()
|
|
{
|
|
auto obe = m_backend_registry.get_object_backend(GNC_ID_TRANS);
|
|
write_objects_t data{this, TRUE, obe.get()};
|
|
|
|
(void)xaccAccountTreeForEachTransaction (
|
|
gnc_book_get_root_account (m_book), write_tx, &data);
|
|
update_progress(101.0);
|
|
return data.is_ok;
|
|
}
|
|
|
|
bool
|
|
GncSqlBackend::write_template_transactions()
|
|
{
|
|
auto obe = m_backend_registry.get_object_backend(GNC_ID_TRANS);
|
|
write_objects_t data{this, true, obe.get()};
|
|
auto ra = gnc_book_get_template_root (m_book);
|
|
if (gnc_account_n_descendants (ra) > 0)
|
|
{
|
|
(void)xaccAccountTreeForEachTransaction (ra, write_tx, &data);
|
|
update_progress(101.0);
|
|
}
|
|
|
|
return data.is_ok;
|
|
}
|
|
|
|
bool
|
|
GncSqlBackend::write_schedXactions()
|
|
{
|
|
GList* schedXactions;
|
|
SchedXaction* tmpSX;
|
|
bool is_ok = true;
|
|
|
|
schedXactions = gnc_book_get_schedxactions (m_book)->sx_list;
|
|
auto obe = m_backend_registry.get_object_backend(GNC_ID_SCHEDXACTION);
|
|
|
|
for (; schedXactions != NULL && is_ok; schedXactions = schedXactions->next)
|
|
{
|
|
tmpSX = static_cast<decltype (tmpSX)> (schedXactions->data);
|
|
is_ok = obe->commit (this, QOF_INSTANCE (tmpSX));
|
|
}
|
|
update_progress(101.0);
|
|
|
|
return is_ok;
|
|
}
|
|
|
|
#pragma GCC diagnostic warning "-Wformat-nonliteral"
|
|
|
|
void
|
|
GncSqlBackend::sync(QofBook* book)
|
|
{
|
|
g_return_if_fail (book != NULL);
|
|
|
|
reset_version_info();
|
|
ENTER ("book=%p, sql_be->book=%p", book, m_book);
|
|
update_progress(101.0);
|
|
|
|
/* Create new tables */
|
|
m_is_pristine_db = true;
|
|
create_tables();
|
|
|
|
/* Save all contents */
|
|
m_book = book;
|
|
auto is_ok = m_conn->begin_transaction();
|
|
|
|
// FIXME: should write the set of commodities that are used
|
|
// write_commodities(sql_be, book);
|
|
if (is_ok)
|
|
{
|
|
auto obe = m_backend_registry.get_object_backend(GNC_ID_BOOK);
|
|
is_ok = obe->commit (this, QOF_INSTANCE (book));
|
|
}
|
|
if (is_ok)
|
|
{
|
|
is_ok = write_accounts();
|
|
}
|
|
if (is_ok)
|
|
{
|
|
is_ok = write_transactions();
|
|
}
|
|
if (is_ok)
|
|
{
|
|
is_ok = write_template_transactions();
|
|
}
|
|
if (is_ok)
|
|
{
|
|
is_ok = write_schedXactions();
|
|
}
|
|
if (is_ok)
|
|
{
|
|
for (auto entry : m_backend_registry)
|
|
std::get<1>(entry)->write (this);
|
|
}
|
|
if (is_ok)
|
|
{
|
|
is_ok = m_conn->commit_transaction();
|
|
}
|
|
if (is_ok)
|
|
{
|
|
m_is_pristine_db = false;
|
|
|
|
/* Mark the session as clean -- though it shouldn't ever get
|
|
* marked dirty with this backend
|
|
*/
|
|
qof_book_mark_session_saved(book);
|
|
}
|
|
else
|
|
{
|
|
set_error (ERR_BACKEND_SERVER_ERR);
|
|
m_conn->rollback_transaction ();
|
|
}
|
|
finish_progress();
|
|
LEAVE ("book=%p", book);
|
|
}
|
|
|
|
/* ================================================================= */
|
|
/* Routines to deal with the creation of multiple books. */
|
|
|
|
void
|
|
GncSqlBackend::begin(QofInstance* inst)
|
|
{
|
|
//g_return_if_fail (inst != NULL);
|
|
|
|
//ENTER (" ");
|
|
//LEAVE ("");
|
|
}
|
|
|
|
void
|
|
GncSqlBackend::rollback(QofInstance* inst)
|
|
{
|
|
//g_return_if_fail (inst != NULL);
|
|
|
|
//ENTER (" ");
|
|
//LEAVE ("");
|
|
}
|
|
|
|
void
|
|
GncSqlBackend::commodity_for_postload_processing(gnc_commodity* commodity)
|
|
{
|
|
m_postload_commodities.push_back(commodity);
|
|
}
|
|
|
|
GncSqlObjectBackendPtr
|
|
GncSqlBackend::get_object_backend(const std::string& type) const noexcept
|
|
{
|
|
return m_backend_registry.get_object_backend(type);
|
|
}
|
|
|
|
|
|
/* Commit_edit handler - find the correct backend handler for this object
|
|
* type and call its commit handler
|
|
*/
|
|
void
|
|
GncSqlBackend::commit (QofInstance* inst)
|
|
{
|
|
sql_backend be_data;
|
|
gboolean is_dirty;
|
|
gboolean is_destroying;
|
|
gboolean is_infant;
|
|
|
|
g_return_if_fail (inst != NULL);
|
|
|
|
if (qof_book_is_readonly(m_book))
|
|
{
|
|
set_error (ERR_BACKEND_READONLY);
|
|
(void)m_conn->rollback_transaction ();
|
|
return;
|
|
}
|
|
/* During initial load where objects are being created, don't commit
|
|
anything, but do mark the object as clean. */
|
|
if (m_loading)
|
|
{
|
|
qof_instance_mark_clean (inst);
|
|
return;
|
|
}
|
|
|
|
// The engine has a PriceDB object but it isn't in the database
|
|
if (strcmp (inst->e_type, "PriceDB") == 0)
|
|
{
|
|
qof_instance_mark_clean (inst);
|
|
qof_book_mark_session_saved (m_book);
|
|
return;
|
|
}
|
|
|
|
ENTER (" ");
|
|
|
|
is_dirty = qof_instance_get_dirty_flag (inst);
|
|
is_destroying = qof_instance_get_destroying (inst);
|
|
is_infant = qof_instance_get_infant (inst);
|
|
|
|
DEBUG ("%s dirty = %d, do_free = %d, infant = %d\n",
|
|
(inst->e_type ? inst->e_type : "(null)"),
|
|
is_dirty, is_destroying, is_infant);
|
|
|
|
if (!is_dirty && !is_destroying)
|
|
{
|
|
LEAVE ("!dirty OR !destroying");
|
|
return;
|
|
}
|
|
|
|
if (!m_conn->begin_transaction ())
|
|
{
|
|
PERR ("begin_transaction failed\n");
|
|
LEAVE ("Rolled back - database transaction begin error");
|
|
return;
|
|
}
|
|
|
|
bool is_ok = true;
|
|
|
|
auto obe = m_backend_registry.get_object_backend(std::string{inst->e_type});
|
|
if (obe != nullptr)
|
|
is_ok = obe->commit(this, inst);
|
|
else
|
|
{
|
|
PERR ("Unknown object type '%s'\n", inst->e_type);
|
|
(void)m_conn->rollback_transaction ();
|
|
|
|
// Don't let unknown items still mark the book as being dirty
|
|
qof_book_mark_session_saved(m_book);
|
|
qof_instance_mark_clean (inst);
|
|
LEAVE ("Rolled back - unknown object type");
|
|
return;
|
|
}
|
|
if (!is_ok)
|
|
{
|
|
// Error - roll it back
|
|
(void)m_conn->rollback_transaction();
|
|
|
|
// This *should* leave things marked dirty
|
|
LEAVE ("Rolled back - database error");
|
|
return;
|
|
}
|
|
|
|
(void)m_conn->commit_transaction ();
|
|
|
|
qof_book_mark_session_saved(m_book);
|
|
qof_instance_mark_clean (inst);
|
|
|
|
LEAVE ("");
|
|
}
|
|
|
|
|
|
/**
|
|
* Sees if the version table exists, and if it does, loads the info into
|
|
* the version hash table. Otherwise, it creates an empty version table.
|
|
*
|
|
* @param be Backend struct
|
|
*/
|
|
void
|
|
GncSqlBackend::init_version_info() noexcept
|
|
{
|
|
|
|
if (m_conn->does_table_exist (VERSION_TABLE_NAME))
|
|
{
|
|
std::string sql {"SELECT * FROM "};
|
|
sql += VERSION_TABLE_NAME;
|
|
auto stmt = m_conn->create_statement_from_sql(sql);
|
|
auto result = m_conn->execute_select_statement (stmt);
|
|
for (const auto& row : *result)
|
|
{
|
|
auto name = row.get_string_at_col (TABLE_COL_NAME);
|
|
unsigned int version = row.get_int_at_col (VERSION_COL_NAME);
|
|
m_versions.push_back(std::make_pair(name, version));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
create_table (VERSION_TABLE_NAME, version_table);
|
|
set_table_version("Gnucash", gnc_prefs_get_long_version ());
|
|
set_table_version("Gnucash-Resave", GNUCASH_RESAVE_VERSION);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets the version table information by removing all version table info.
|
|
* It also recreates the version table in the db.
|
|
*
|
|
* @param be Backend struct
|
|
* @return TRUE if successful, FALSE if error
|
|
*/
|
|
bool
|
|
GncSqlBackend::reset_version_info() noexcept
|
|
{
|
|
bool ok = create_table (VERSION_TABLE_NAME, version_table);
|
|
m_versions.clear();
|
|
set_table_version ("Gnucash", gnc_prefs_get_long_version ());
|
|
set_table_version ("Gnucash-Resave", GNUCASH_RESAVE_VERSION);
|
|
return ok;
|
|
}
|
|
|
|
/**
|
|
* Finalizes the version table info by destroying the hash table.
|
|
*
|
|
* @param be Backend struct
|
|
*/
|
|
void
|
|
GncSqlBackend::finalize_version_info() noexcept
|
|
{
|
|
m_versions.clear();
|
|
}
|
|
|
|
unsigned int
|
|
GncSqlBackend::get_table_version(const std::string& table_name) const noexcept
|
|
{
|
|
/* If the db is pristine because it's being saved, the table does not exist. */
|
|
if (m_is_pristine_db)
|
|
return 0;
|
|
|
|
auto version = std::find_if(m_versions.begin(), m_versions.end(),
|
|
[table_name](const VersionPair& version) {
|
|
return version.first == table_name; });
|
|
if (version != m_versions.end())
|
|
return version->second;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Registers the version for a table. Registering involves updating the
|
|
* db version table and also the hash table.
|
|
*
|
|
* @param be Backend struct
|
|
* @param table_name Table name
|
|
* @param version Version number
|
|
* @return TRUE if successful, FALSE if unsuccessful
|
|
*/
|
|
bool
|
|
GncSqlBackend::set_table_version (const std::string& table_name,
|
|
uint_t version) noexcept
|
|
{
|
|
g_return_val_if_fail (version > 0, false);
|
|
|
|
unsigned int cur_version{0};
|
|
std::stringstream sql;
|
|
auto ver_entry = std::find_if(m_versions.begin(), m_versions.end(),
|
|
[table_name](const VersionPair& ver) {
|
|
return ver.first == table_name; });
|
|
if (ver_entry != m_versions.end())
|
|
cur_version = ver_entry->second;
|
|
if (cur_version != version)
|
|
{
|
|
if (cur_version == 0)
|
|
{
|
|
sql << "INSERT INTO " << VERSION_TABLE_NAME << " VALUES('" <<
|
|
table_name << "'," << version <<")";
|
|
m_versions.push_back(std::make_pair(table_name, version));
|
|
}
|
|
else
|
|
{
|
|
sql << "UPDATE " << VERSION_TABLE_NAME << " SET " <<
|
|
VERSION_COL_NAME << "=" << version << " WHERE " <<
|
|
TABLE_COL_NAME << "='" << table_name << "'";
|
|
ver_entry->second = version;
|
|
}
|
|
auto stmt = create_statement_from_sql(sql.str());
|
|
auto status = execute_nonselect_statement (stmt);
|
|
if (status == -1)
|
|
{
|
|
PERR ("SQL error: %s\n", sql.str().c_str());
|
|
qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
GncSqlBackend::upgrade_table (const std::string& table_name,
|
|
const EntryVec& col_table) noexcept
|
|
{
|
|
DEBUG ("Upgrading %s table\n", table_name.c_str());
|
|
|
|
auto temp_table_name = table_name + "_new";
|
|
create_table (temp_table_name, col_table);
|
|
std::stringstream sql;
|
|
sql << "INSERT INTO " << temp_table_name << " SELECT * FROM " << table_name;
|
|
auto stmt = create_statement_from_sql(sql.str());
|
|
execute_nonselect_statement(stmt);
|
|
|
|
sql.str("");
|
|
sql << "DROP TABLE " << table_name;
|
|
stmt = create_statement_from_sql(sql.str());
|
|
execute_nonselect_statement(stmt);
|
|
|
|
sql.str("");
|
|
sql << "ALTER TABLE " << temp_table_name << " RENAME TO " << table_name;
|
|
stmt = create_statement_from_sql(sql.str());
|
|
execute_nonselect_statement(stmt);
|
|
}
|
|
|
|
static inline PairVec
|
|
get_object_values (QofIdTypeConst obj_name,
|
|
gpointer pObject, const EntryVec& table)
|
|
{
|
|
PairVec vec;
|
|
|
|
for (auto const& table_row : table)
|
|
{
|
|
if (!(table_row->is_autoincr()))
|
|
{
|
|
table_row->add_to_query (obj_name, pObject, vec);
|
|
}
|
|
}
|
|
return vec;
|
|
}
|
|
|
|
bool
|
|
GncSqlBackend::object_in_db (const char* table_name, QofIdTypeConst obj_name,
|
|
const gpointer pObject, const EntryVec& table) const noexcept
|
|
{
|
|
guint count;
|
|
g_return_val_if_fail (table_name != nullptr, false);
|
|
g_return_val_if_fail (obj_name != nullptr, false);
|
|
g_return_val_if_fail (pObject != nullptr, false);
|
|
|
|
/* SELECT * FROM */
|
|
auto sql = std::string{"SELECT "} + table[0]->name() + " FROM " + table_name;
|
|
auto stmt = create_statement_from_sql(sql.c_str());
|
|
assert (stmt != nullptr);
|
|
|
|
/* WHERE */
|
|
PairVec values{get_object_values(obj_name, pObject, table)};
|
|
/* We want only the first item in the table, which should be the PK. */
|
|
values.resize(1);
|
|
stmt->add_where_cond(obj_name, values);
|
|
auto result = execute_select_statement (stmt);
|
|
return (result != nullptr && result->size() > 0);
|
|
}
|
|
|
|
bool
|
|
GncSqlBackend::do_db_operation (E_DB_OPERATION op, const char* table_name,
|
|
QofIdTypeConst obj_name, gpointer pObject,
|
|
const EntryVec& table) const noexcept
|
|
{
|
|
GncSqlStatementPtr stmt;
|
|
|
|
g_return_val_if_fail (table_name != nullptr, false);
|
|
g_return_val_if_fail (obj_name != nullptr, false);
|
|
g_return_val_if_fail (pObject != nullptr, false);
|
|
|
|
switch(op)
|
|
{
|
|
case OP_DB_INSERT:
|
|
stmt = build_insert_statement (table_name, obj_name, pObject, table);
|
|
break;
|
|
case OP_DB_UPDATE:
|
|
stmt = build_update_statement (table_name, obj_name, pObject, table);
|
|
break;
|
|
case OP_DB_DELETE:
|
|
stmt = build_delete_statement (table_name, obj_name, pObject, table);
|
|
break;
|
|
}
|
|
if (stmt == nullptr)
|
|
return false;
|
|
return (execute_nonselect_statement(stmt) != -1);
|
|
}
|
|
|
|
bool
|
|
GncSqlBackend::save_commodity(gnc_commodity* comm) noexcept
|
|
{
|
|
if (comm == nullptr) return false;
|
|
QofInstance* inst = QOF_INSTANCE(comm);
|
|
auto obe = m_backend_registry.get_object_backend(std::string(inst->e_type));
|
|
if (obe && !obe->instance_in_db(this, inst))
|
|
return obe->commit(this, inst);
|
|
return true;
|
|
}
|
|
|
|
GncSqlStatementPtr
|
|
GncSqlBackend::build_insert_statement (const char* table_name,
|
|
QofIdTypeConst obj_name,
|
|
gpointer pObject,
|
|
const EntryVec& table) const noexcept
|
|
{
|
|
GncSqlStatementPtr stmt;
|
|
PairVec col_values;
|
|
std::ostringstream sql;
|
|
|
|
g_return_val_if_fail (table_name != nullptr, nullptr);
|
|
g_return_val_if_fail (obj_name != nullptr, nullptr);
|
|
g_return_val_if_fail (pObject != nullptr, nullptr);
|
|
PairVec values{get_object_values(obj_name, pObject, table)};
|
|
|
|
sql << "INSERT INTO " << table_name <<"(";
|
|
for (auto const& col_value : values)
|
|
{
|
|
if (col_value != *values.begin())
|
|
sql << ",";
|
|
sql << col_value.first;
|
|
}
|
|
|
|
sql << ") VALUES(";
|
|
for (auto col_value : values)
|
|
{
|
|
if (col_value != *values.begin())
|
|
sql << ",";
|
|
sql << col_value.second;
|
|
}
|
|
sql << ")";
|
|
|
|
stmt = create_statement_from_sql(sql.str());
|
|
return stmt;
|
|
}
|
|
|
|
GncSqlStatementPtr
|
|
GncSqlBackend::build_update_statement(const gchar* table_name,
|
|
QofIdTypeConst obj_name, gpointer pObject,
|
|
const EntryVec& table) const noexcept
|
|
{
|
|
GncSqlStatementPtr stmt;
|
|
std::ostringstream sql;
|
|
|
|
g_return_val_if_fail (table_name != nullptr, nullptr);
|
|
g_return_val_if_fail (obj_name != nullptr, nullptr);
|
|
g_return_val_if_fail (pObject != nullptr, nullptr);
|
|
|
|
|
|
PairVec values{get_object_values (obj_name, pObject, table)};
|
|
|
|
// Create the SQL statement
|
|
sql << "UPDATE " << table_name << " SET ";
|
|
|
|
for (auto const& col_value : values)
|
|
{
|
|
if (col_value != *values.begin())
|
|
sql << ",";
|
|
sql << col_value.first << "=" <<
|
|
col_value.second;
|
|
}
|
|
|
|
stmt = create_statement_from_sql(sql.str());
|
|
/* We want our where condition to be just the first column and
|
|
* value, i.e. the guid of the object.
|
|
*/
|
|
values.erase(values.begin() + 1, values.end());
|
|
stmt->add_where_cond(obj_name, values);
|
|
return stmt;
|
|
}
|
|
|
|
GncSqlStatementPtr
|
|
GncSqlBackend::build_delete_statement(const gchar* table_name,
|
|
QofIdTypeConst obj_name,
|
|
gpointer pObject,
|
|
const EntryVec& table) const noexcept
|
|
{
|
|
std::ostringstream sql;
|
|
|
|
g_return_val_if_fail (table_name != nullptr, nullptr);
|
|
g_return_val_if_fail (obj_name != nullptr, nullptr);
|
|
g_return_val_if_fail (pObject != nullptr, nullptr);
|
|
|
|
sql << "DELETE FROM " << table_name;
|
|
auto stmt = create_statement_from_sql (sql.str());
|
|
|
|
/* WHERE */
|
|
PairVec values;
|
|
table[0]->add_to_query (obj_name, pObject, values);
|
|
PairVec col_values{values[0]};
|
|
stmt->add_where_cond (obj_name, col_values);
|
|
|
|
return stmt;
|
|
}
|
|
|
|
GncSqlBackend::ObjectBackendRegistry::ObjectBackendRegistry()
|
|
{
|
|
register_backend(std::make_shared<GncSqlBookBackend>());
|
|
register_backend(std::make_shared<GncSqlCommodityBackend>());
|
|
register_backend(std::make_shared<GncSqlAccountBackend>());
|
|
register_backend(std::make_shared<GncSqlBudgetBackend>());
|
|
register_backend(std::make_shared<GncSqlPriceBackend>());
|
|
register_backend(std::make_shared<GncSqlTransBackend>());
|
|
register_backend(std::make_shared<GncSqlSplitBackend>());
|
|
register_backend(std::make_shared<GncSqlSlotsBackend>());
|
|
register_backend(std::make_shared<GncSqlRecurrenceBackend>());
|
|
register_backend(std::make_shared<GncSqlSchedXactionBackend>());
|
|
register_backend(std::make_shared<GncSqlLotsBackend>());
|
|
register_backend(std::make_shared<GncSqlBillTermBackend>());
|
|
register_backend(std::make_shared<GncSqlCustomerBackend>());
|
|
register_backend(std::make_shared<GncSqlEmployeeBackend>());
|
|
register_backend(std::make_shared<GncSqlEntryBackend>());
|
|
register_backend(std::make_shared<GncSqlInvoiceBackend>());
|
|
register_backend(std::make_shared<GncSqlJobBackend>());
|
|
register_backend(std::make_shared<GncSqlOrderBackend>());
|
|
register_backend(std::make_shared<GncSqlTaxTableBackend>());
|
|
register_backend(std::make_shared<GncSqlVendorBackend>());
|
|
}
|
|
|
|
void
|
|
GncSqlBackend::ObjectBackendRegistry::register_backend(OBEEntry&& entry) noexcept
|
|
{
|
|
m_registry.emplace_back(entry);
|
|
}
|
|
|
|
void
|
|
GncSqlBackend::ObjectBackendRegistry::register_backend(GncSqlObjectBackendPtr obe) noexcept
|
|
{
|
|
m_registry.emplace_back(make_tuple(std::string{obe->type()}, obe));
|
|
}
|
|
|
|
GncSqlObjectBackendPtr
|
|
GncSqlBackend::ObjectBackendRegistry::get_object_backend(const std::string& type) const
|
|
{
|
|
auto entry = std::find_if(m_registry.begin(), m_registry.end(),
|
|
[type](const OBEEntry& entry){
|
|
return type == std::get<0>(entry);
|
|
});
|
|
if (entry == m_registry.end())
|
|
return nullptr;
|
|
|
|
return std::get<1>(*entry);
|
|
}
|