/********************************************************************\ * gnc-tax-table-sql.c -- tax table sql implementation * * * * 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-tax-table-sql.c * @brief load and save address data to SQL * @author Copyright (c) 2007-2008 Phil Longstaff * * This file implements the top-level QofBackend API for saving/ * restoring data to/from an SQL database */ #include #include #include #include #include #include "gncEntry.h" #include "gncTaxTableP.h" #include #include #include #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-slots-sql.h" #include "gnc-tax-table-sql.h" #define _GNC_MOD_NAME GNC_ID_TAXTABLE static QofLogModule log_module = G_LOG_DOMAIN; typedef struct { GncSqlBackend* be; const GncGUID* guid; } guid_info_t; static gpointer get_obj_guid (gpointer pObject, const QofParam* param); static void set_obj_guid (gpointer pObject, gpointer pValue); static gpointer bt_get_parent (gpointer pObject); static void tt_set_parent (gpointer pObject, gpointer pValue); static void tt_set_parent_guid (gpointer pObject, gpointer pValue); #define MAX_NAME_LEN 50 #define TT_TABLE_NAME "taxtables" #define TT_TABLE_VERSION 2 static EntryVec tt_col_table ({ gnc_sql_make_table_entry("guid", 0, COL_NNUL | COL_PKEY, "guid" ), gnc_sql_make_table_entry("name", MAX_NAME_LEN, COL_NNUL, "name" ), gnc_sql_make_table_entry("refcount", 0, COL_NNUL, "ref-count" ), gnc_sql_make_table_entry("invisible", 0, COL_NNUL, "invisible" ), /* gnc_sql_make_table_entry("child", 0, 0, get_child, (QofSetterFunc)gncTaxTableSetChild ), */ gnc_sql_make_table_entry("parent", 0, 0, (QofAccessFunc)bt_get_parent, tt_set_parent ), }); static EntryVec tt_parent_col_table ({ gnc_sql_make_table_entry("parent", 0, 0, nullptr, tt_set_parent_guid ), }); #define TTENTRIES_TABLE_NAME "taxtable_entries" #define TTENTRIES_TABLE_VERSION 3 static EntryVec ttentries_col_table ({ gnc_sql_make_table_entry( "id", 0, COL_PKEY | COL_NNUL | COL_AUTOINC), gnc_sql_make_table_entry("taxtable", 0, COL_NNUL, (QofAccessFunc)gncTaxTableEntryGetTable, set_obj_guid), gnc_sql_make_table_entry("account", 0, COL_NNUL, (QofAccessFunc)gncTaxTableEntryGetAccount, (QofSetterFunc)gncTaxTableEntrySetAccount), gnc_sql_make_table_entry("amount", 0, COL_NNUL, (QofAccessFunc)gncTaxTableEntryGetAmount, (QofSetterFunc)gncTaxTableEntrySetAmount), gnc_sql_make_table_entry("type", 0, COL_NNUL, (QofAccessFunc)gncTaxTableEntryGetType, (QofSetterFunc)gncTaxTableEntrySetType), }); /* Special column table because we need to be able to access the table by a column other than the primary key */ static EntryVec guid_col_table ({ gnc_sql_make_table_entry("taxtable", 0, 0, get_obj_guid, set_obj_guid), }); GncSqlTaxTableBackend::GncSqlTaxTableBackend() : GncSqlObjectBackend(TT_TABLE_VERSION, GNC_ID_TAXTABLE, TT_TABLE_NAME, tt_col_table) {} struct TaxTblParentGuid { GncTaxTable* tt; GncGUID guid; bool have_guid; }; using TaxTblParentGuidPtr = TaxTblParentGuid*; using TaxTblParentGuidVec = std::vector; static gpointer get_obj_guid (gpointer pObject, const QofParam* param) { guid_info_t* pInfo = (guid_info_t*)pObject; g_return_val_if_fail (pInfo != NULL, NULL); return (gpointer)pInfo->guid; } static void set_obj_guid (gpointer pObject, gpointer pValue) { // Nowhere to put the GncGUID } static gpointer bt_get_parent (gpointer pObject) { const GncTaxTable* tt; const GncTaxTable* pParent; const GncGUID* parent_guid; g_return_val_if_fail (pObject != NULL, NULL); g_return_val_if_fail (GNC_IS_TAXTABLE (pObject), NULL); tt = GNC_TAXTABLE (pObject); pParent = gncTaxTableGetParent (tt); if (pParent == NULL) { parent_guid = NULL; } else { parent_guid = qof_instance_get_guid (QOF_INSTANCE (pParent)); } return (gpointer)parent_guid; } static void tt_set_parent (gpointer data, gpointer value) { GncTaxTable* tt; GncTaxTable* parent; QofBook* pBook; GncGUID* guid = (GncGUID*)value; g_return_if_fail (data != NULL); g_return_if_fail (GNC_IS_TAXTABLE (data)); tt = GNC_TAXTABLE (data); pBook = qof_instance_get_book (QOF_INSTANCE (tt)); if (guid != NULL) { parent = gncTaxTableLookup (pBook, guid); if (parent != NULL) { gncTaxTableSetParent (tt, parent); gncTaxTableSetChild (parent, tt); } } } static void tt_set_parent_guid (gpointer pObject, gpointer pValue) { g_return_if_fail (pObject != NULL); g_return_if_fail (pValue != NULL); auto s = static_cast(pObject); s->guid = *static_cast(pValue); s->have_guid = true; } static void load_single_ttentry (GncSqlBackend* sql_be, GncSqlRow& row, GncTaxTable* tt) { GncTaxTableEntry* e = gncTaxTableEntryCreate (); g_return_if_fail (sql_be != NULL); g_return_if_fail (tt != NULL); gnc_sql_load_object (sql_be, row, GNC_ID_TAXTABLE, e, ttentries_col_table); gncTaxTableAddEntry (tt, e); } static void load_taxtable_entries (GncSqlBackend* sql_be, GncTaxTable* tt) { gchar guid_buf[GUID_ENCODING_LENGTH + 1]; GValue value; gchar* buf; g_return_if_fail (sql_be != NULL); g_return_if_fail (tt != NULL); guid_to_string_buff (qof_instance_get_guid (QOF_INSTANCE (tt)), guid_buf); memset (&value, 0, sizeof (GValue)); g_value_init (&value, G_TYPE_STRING); g_value_set_string (&value, guid_buf); buf = g_strdup_printf ("SELECT * FROM %s WHERE taxtable='%s'", TTENTRIES_TABLE_NAME, guid_buf); auto stmt = sql_be->create_statement_from_sql (buf); g_free (buf); auto result = sql_be->execute_select_statement(stmt); for (auto row : *result) load_single_ttentry (sql_be, row, tt); } static void load_single_taxtable (GncSqlBackend* sql_be, GncSqlRow& row, TaxTblParentGuidVec& l_tt_needing_parents) { const GncGUID* guid; GncTaxTable* tt; g_return_if_fail (sql_be != NULL); guid = gnc_sql_load_guid (sql_be, row); tt = gncTaxTableLookup (sql_be->book(), guid); if (tt == nullptr) { tt = gncTaxTableCreate (sql_be->book()); } gnc_sql_load_object (sql_be, row, GNC_ID_TAXTABLE, tt, tt_col_table); gnc_sql_slots_load (sql_be, QOF_INSTANCE (tt)); load_taxtable_entries (sql_be, tt); /* If the tax table doesn't have a parent, it might be because it hasn't been loaded yet. if so, add this tax table to the list of tax tables with no parent, along with the parent GncGUID so that after they are all loaded, the parents can be fixed up. */ if (gncTaxTableGetParent (tt) == NULL) { TaxTblParentGuid s; s.tt = tt; s.have_guid = false; gnc_sql_load_object (sql_be, row, GNC_ID_TAXTABLE, &s, tt_parent_col_table); if (s.have_guid) l_tt_needing_parents.push_back(new TaxTblParentGuid(s)); } qof_instance_mark_clean (QOF_INSTANCE (tt)); } void GncSqlTaxTableBackend::load_all (GncSqlBackend* sql_be) { g_return_if_fail (sql_be != NULL); /* First time, create the query */ std::stringstream sql; sql << "SELECT * FROM " << TT_TABLE_NAME; auto stmt = sql_be->create_statement_from_sql(sql.str()); auto result = sql_be->execute_select_statement(stmt); TaxTblParentGuidVec tt_needing_parents; for (auto row : *result) load_single_taxtable (sql_be, row, tt_needing_parents); /* While there are items on the list of taxtables needing parents, try to see if the parent has now been loaded. Theory says that if items are removed from the front and added to the back if the parent is still not available, then eventually, the list will shrink to size 0. */ if (!tt_needing_parents.empty()) { bool progress_made = true; std::reverse(tt_needing_parents.begin(), tt_needing_parents.end()); auto end = tt_needing_parents.end(); while (progress_made) { progress_made = false; end = std::remove_if(tt_needing_parents.begin(), end, [&](TaxTblParentGuidPtr s) { auto pBook = qof_instance_get_book (QOF_INSTANCE (s->tt)); auto parent = gncTaxTableLookup (pBook, &s->guid); if (parent != nullptr) { tt_set_parent (s->tt, &s->guid); progress_made = true; delete s; return true; } return false; }); } } } /* ================================================================= */ void GncSqlTaxTableBackend::create_tables (GncSqlBackend* sql_be) { gint version; g_return_if_fail (sql_be != NULL); version = sql_be->get_table_version( TT_TABLE_NAME); if (version == 0) { sql_be->create_table(TT_TABLE_NAME, TT_TABLE_VERSION, tt_col_table); } else if (version < m_version) { /* Upgrade 64 bit int handling */ sql_be->upgrade_table(TT_TABLE_NAME, tt_col_table); sql_be->set_table_version (TT_TABLE_NAME, TT_TABLE_VERSION); PINFO ("Taxtables table upgraded from version 1 to version %d\n", TT_TABLE_VERSION); } version = sql_be->get_table_version( TTENTRIES_TABLE_NAME); if (version == 0) { sql_be->create_table(TTENTRIES_TABLE_NAME, TTENTRIES_TABLE_VERSION, ttentries_col_table); } else if (version < TTENTRIES_TABLE_VERSION) { /* Upgrade 64 bit int handling */ sql_be->upgrade_table(TTENTRIES_TABLE_NAME, ttentries_col_table); sql_be->set_table_version (TTENTRIES_TABLE_NAME, TTENTRIES_TABLE_VERSION); PINFO ("Taxtable entries table upgraded from version 1 to version %d\n", TTENTRIES_TABLE_VERSION); } } /* ================================================================= */ static gboolean delete_all_tt_entries (GncSqlBackend* sql_be, const GncGUID* guid) { guid_info_t guid_info; g_return_val_if_fail (sql_be != NULL, FALSE); g_return_val_if_fail (guid != NULL, FALSE); guid_info.be = sql_be; guid_info.guid = guid; return sql_be->do_db_operation(OP_DB_DELETE, TTENTRIES_TABLE_NAME, TTENTRIES_TABLE_NAME, &guid_info, guid_col_table); } static gboolean save_tt_entries (GncSqlBackend* sql_be, const GncGUID* guid, GList* entries) { GList* entry; gboolean is_ok; g_return_val_if_fail (sql_be != NULL, FALSE); g_return_val_if_fail (guid != NULL, FALSE); /* First, delete the old entries for this object */ is_ok = delete_all_tt_entries (sql_be, guid); for (entry = entries; entry != NULL && is_ok; entry = entry->next) { GncTaxTableEntry* e = (GncTaxTableEntry*)entry->data; is_ok = sql_be->do_db_operation(OP_DB_INSERT, TTENTRIES_TABLE_NAME, GNC_ID_TAXTABLE, e, ttentries_col_table); } return is_ok; } bool GncSqlTaxTableBackend::commit (GncSqlBackend* sql_be, QofInstance* inst) { GncTaxTable* tt; const GncGUID* guid; E_DB_OPERATION op; gboolean is_infant; gboolean is_ok; g_return_val_if_fail (inst != NULL, FALSE); g_return_val_if_fail (GNC_IS_TAXTABLE (inst), FALSE); g_return_val_if_fail (sql_be != NULL, FALSE); tt = GNC_TAXTABLE (inst); 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; } is_ok = sql_be->do_db_operation(op, TT_TABLE_NAME, GNC_ID_TAXTABLE, tt, tt_col_table); if (is_ok) { // Now, commit or delete any slots and tax table entries 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) { is_ok = save_tt_entries (sql_be, guid, gncTaxTableGetEntries (tt)); } } else { is_ok = gnc_sql_slots_delete (sql_be, guid); if (is_ok) { is_ok = delete_all_tt_entries (sql_be, guid); } } } return is_ok; } /* ================================================================= */ static void save_next_taxtable (QofInstance* inst, gpointer data) { auto s = reinterpret_cast(data); if (s->is_ok) { s->commit (inst); } } bool GncSqlTaxTableBackend::write (GncSqlBackend* sql_be) { g_return_val_if_fail (sql_be != NULL, FALSE); write_objects_t data{sql_be, true, this}; qof_object_foreach (GNC_ID_TAXTABLE, sql_be->book(), save_next_taxtable, &data); return data.is_ok; } /* ================================================================= */ template<> void GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, GncSqlRow& row, QofIdTypeConst obj_name, gpointer pObject) const noexcept { load_from_guid_ref(row, obj_name, pObject, [sql_be](GncGUID* g){ return gncTaxTableLookup(sql_be->book(), g); }); } template<> void GncSqlColumnTableEntryImpl::add_to_table(ColVec& vec) const noexcept { add_objectref_guid_to_table(vec); } template<> void GncSqlColumnTableEntryImpl::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 ===================== */