/******************************************************************** * gnc-slots-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-slots-sql.c * @brief load and save data to SQL * @author Copyright (c) 2006-2008 Phil Longstaff * * This file implements the top-level QofBackend API for saving/ * restoring data to/from an SQL db */ #include #include #include #include #include #ifdef S_SPLINT_S #include "splint-defs.h" #endif #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 static QofLogModule log_module = G_LOG_DOMAIN; #define TABLE_NAME "slots" #define TABLE_VERSION 4 typedef enum { NONE, FRAME, LIST } context_t; struct slot_info_t { GncSqlBackend* be; const GncGUID* guid; gboolean is_ok; KvpFrame* pKvpFrame; KvpValue::Type value_type; GList* pList; context_t context; KvpValue* pKvpValue; std::string path; std::string parent_path; }; static gpointer get_obj_guid (gpointer pObject); static void set_obj_guid (void); static gpointer get_path (gpointer pObject); static void set_path (gpointer pObject, gpointer pValue); static KvpValue::Type get_slot_type (gpointer pObject); static void set_slot_type (gpointer pObject, gpointer pValue); static gint64 get_int64_val (gpointer pObject); static void set_int64_val (gpointer pObject, gint64 pValue); static gpointer get_string_val (gpointer pObject); static void set_string_val (gpointer pObject, gpointer pValue); static gpointer get_double_val (gpointer pObject); static void set_double_val (gpointer pObject, gpointer pValue); static time64 get_time_val (gpointer pObject); static void set_time_val (gpointer pObject, time64 t); static gpointer get_guid_val (gpointer pObject); static void set_guid_val (gpointer pObject, gpointer pValue); static gnc_numeric get_numeric_val (gpointer pObject); static void set_numeric_val (gpointer pObject, gnc_numeric value); static GDate* get_gdate_val (gpointer pObject); static void set_gdate_val (gpointer pObject, GDate* value); static slot_info_t* slot_info_copy (slot_info_t* pInfo, GncGUID* guid); static void slots_load_info (slot_info_t* pInfo); #define SLOT_MAX_PATHNAME_LEN 4096 #define SLOT_MAX_STRINGVAL_LEN 4096 enum { id_col = 0, obj_guid_col, name_col, slot_type_col, int64_val_col, string_val_col, double_val_col, time_val_col, guid_val_col, numeric_val_col, gdate_val_col }; static const EntryVec col_table { /* col_name, col_type, size, flags, g0bj_param_name, qof_param_name, getter, setter */ gnc_sql_make_table_entry( "id", 0, COL_PKEY | COL_NNUL | COL_AUTOINC), gnc_sql_make_table_entry("obj_guid", 0, COL_NNUL, (QofAccessFunc)get_obj_guid, (QofSetterFunc)set_obj_guid), gnc_sql_make_table_entry("name", SLOT_MAX_PATHNAME_LEN, COL_NNUL, (QofAccessFunc)get_path, set_path), gnc_sql_make_table_entry("slot_type", 0, COL_NNUL, (QofAccessFunc)get_slot_type, set_slot_type), gnc_sql_make_table_entry("int64_val", 0, 0, (QofAccessFunc)get_int64_val, (QofSetterFunc)set_int64_val), gnc_sql_make_table_entry("string_val", SLOT_MAX_PATHNAME_LEN, 0, (QofAccessFunc)get_string_val, set_string_val), gnc_sql_make_table_entry("double_val", 0, 0, (QofAccessFunc)get_double_val, set_double_val), gnc_sql_make_table_entry("timespec_val", 0, 0, (QofAccessFunc)get_time_val, (QofSetterFunc)set_time_val), gnc_sql_make_table_entry("guid_val", 0, 0, (QofAccessFunc)get_guid_val, set_guid_val), gnc_sql_make_table_entry("numeric_val", 0, 0, (QofAccessFunc)get_numeric_val, (QofSetterFunc)set_numeric_val), gnc_sql_make_table_entry("gdate_val", 0, 0, (QofAccessFunc)get_gdate_val, (QofSetterFunc)set_gdate_val), }; static void _retrieve_guid_ (gpointer pObject, gpointer pValue) { GncGUID* pGuid = (GncGUID*)pObject; GncGUID* guid = (GncGUID*)pValue; g_return_if_fail (pObject != NULL); g_return_if_fail (pValue != NULL); *pGuid = *guid; } /* Special column table because we need to be able to access the table by a column other than the primary key */ static const EntryVec obj_guid_col_table { gnc_sql_make_table_entry("obj_guid", 0, 0, (QofAccessFunc)get_obj_guid, _retrieve_guid_), }; static const EntryVec gdate_col_table { gnc_sql_make_table_entry("gdate_val", 0, 0), }; GncSqlSlotsBackend::GncSqlSlotsBackend() : GncSqlObjectBackend(TABLE_VERSION, GNC_ID_ACCOUNT, TABLE_NAME, col_table) {} /* ================================================================= */ static std::string get_key (slot_info_t* pInfo) { if (!pInfo) return ""; auto path = pInfo->path; path.erase (0, pInfo->parent_path.size()); return path; } static void set_slot_from_value (slot_info_t* pInfo, KvpValue* pValue) { g_return_if_fail (pInfo != NULL); g_return_if_fail (pValue != NULL); switch (pInfo->context) { case FRAME: { auto key = get_key (pInfo); pInfo->pKvpFrame->set ({key}, pValue); break; } case LIST: { pInfo->pList = g_list_append (pInfo->pList, pValue); break; } case NONE: default: { auto key = get_key (pInfo); auto path = pInfo->parent_path; auto frame = pInfo->pKvpFrame; if (!path.empty()) { frame->set_path ({path, key}, pValue); } else frame->set ({key}, pValue); break; } } } static gpointer get_obj_guid (gpointer pObject) { slot_info_t* pInfo = (slot_info_t*)pObject; g_return_val_if_fail (pObject != NULL, NULL); return (gpointer)pInfo->guid; } static void set_obj_guid (void) { // Nowhere to put the GncGUID } static gpointer get_path (gpointer pObject) { slot_info_t* pInfo = (slot_info_t*)pObject; g_return_val_if_fail (pObject != NULL, NULL); return (gpointer)pInfo->path.c_str(); } static void set_path (gpointer pObject, gpointer pValue) { slot_info_t* pInfo = (slot_info_t*)pObject; pInfo->path = static_cast(pValue); if (pInfo->path.find (pInfo->parent_path) != 0) pInfo->parent_path.clear(); } static KvpValue::Type get_slot_type (gpointer pObject) { slot_info_t* pInfo = (slot_info_t*)pObject; g_return_val_if_fail (pObject != NULL, KvpValue::Type::INVALID); // return (gpointer)kvp_value_get_type( pInfo->pKvpValue ); return pInfo->value_type; } static void set_slot_type (gpointer pObject, gpointer pValue) { slot_info_t* pInfo = (slot_info_t*)pObject; g_return_if_fail (pObject != NULL); g_return_if_fail (pValue != NULL); pInfo->value_type = static_cast (GPOINTER_TO_INT (pValue)); } static gint64 get_int64_val (gpointer pObject) { slot_info_t* pInfo = (slot_info_t*)pObject; g_return_val_if_fail (pObject != NULL, 0); if (pInfo->pKvpValue->get_type () == KvpValue::Type::INT64) { return pInfo->pKvpValue->get (); } else { return 0; } } static void set_int64_val (gpointer pObject, gint64 value) { slot_info_t* pInfo = (slot_info_t*)pObject; KvpValue* pValue = NULL; g_return_if_fail (pObject != NULL); if (pInfo->value_type != KvpValue::Type::INT64) return; pValue = new KvpValue {value}; set_slot_from_value (pInfo, pValue); } static gpointer get_string_val (gpointer pObject) { slot_info_t* pInfo = (slot_info_t*)pObject; g_return_val_if_fail (pObject != NULL, NULL); if (pInfo->pKvpValue->get_type () == KvpValue::Type::STRING) { return (gpointer)pInfo->pKvpValue->get (); } else { return NULL; } } static void set_string_val (gpointer pObject, gpointer pValue) { slot_info_t* pInfo = (slot_info_t*)pObject; g_return_if_fail (pObject != NULL); if (pInfo->value_type != KvpValue::Type::STRING || pValue == NULL) return; auto value = new KvpValue {g_strdup(static_cast (pValue))}; set_slot_from_value (pInfo, value); } static gpointer get_double_val (gpointer pObject) { static double d_val; /* static so that we can return it. */ g_return_val_if_fail (pObject != NULL, NULL); auto pInfo = static_cast(pObject); if (pInfo->pKvpValue->get_type () == KvpValue::Type::DOUBLE) { d_val = pInfo->pKvpValue->get (); return (gpointer)&d_val; } else { return NULL; } } static void set_double_val (gpointer pObject, gpointer pValue) { slot_info_t* pInfo = (slot_info_t*)pObject; KvpValue* value = NULL; g_return_if_fail (pObject != NULL); if (pInfo->value_type != KvpValue::Type::DOUBLE || pValue == NULL) return; value = new KvpValue {* (static_cast (pValue))}; set_slot_from_value (pInfo, value); } static time64 get_time_val (gpointer pObject) { slot_info_t* pInfo = (slot_info_t*)pObject; g_return_val_if_fail (pObject != NULL, 0); //if( kvp_value_get_type( pInfo->pKvpValue ) == KvpValue::Type::TIME64 ) { auto t = pInfo->pKvpValue->get (); return t.t; } static void set_time_val (gpointer pObject, time64 time) { slot_info_t* pInfo = (slot_info_t*)pObject; KvpValue* value = NULL; Time64 t{time}; g_return_if_fail (pObject != NULL); if (pInfo->value_type != KvpValue::Type::TIME64) return; value = new KvpValue {t}; set_slot_from_value (pInfo, value); } static gpointer get_guid_val (gpointer pObject) { slot_info_t* pInfo = (slot_info_t*)pObject; g_return_val_if_fail (pObject != NULL, NULL); if (pInfo->pKvpValue->get_type () == KvpValue::Type::GUID) { return (gpointer)pInfo->pKvpValue->get (); } else { return NULL; } } static void set_guid_val (gpointer pObject, gpointer pValue) { slot_info_t* pInfo = (slot_info_t*)pObject; g_return_if_fail (pObject != NULL); if (pValue == NULL) return; switch (pInfo->value_type) { case KvpValue::Type::GUID: { auto new_guid = guid_copy (static_cast (pValue)); set_slot_from_value (pInfo, new KvpValue {new_guid}); break; } case KvpValue::Type::GLIST: { slot_info_t* newInfo = slot_info_copy (pInfo, (GncGUID*)pValue); KvpValue* pValue = NULL; auto key = get_key (pInfo); newInfo->context = LIST; slots_load_info (newInfo); pValue = new KvpValue {newInfo->pList}; pInfo->pKvpFrame->set ({key.c_str()}, pValue); delete newInfo; break; } case KvpValue::Type::FRAME: { slot_info_t* newInfo = slot_info_copy (pInfo, (GncGUID*)pValue) ; auto newFrame = new KvpFrame; newInfo->pKvpFrame = newFrame; switch (pInfo->context) { case LIST: { auto value = new KvpValue {newFrame}; newInfo->path = get_key (pInfo); pInfo->pList = g_list_append (pInfo->pList, value); break; } case FRAME: default: { auto key = get_key (pInfo); pInfo->pKvpFrame->set ({key.c_str()}, new KvpValue {newFrame}); break; } } newInfo->context = FRAME; slots_load_info (newInfo); delete newInfo; break; } default: break; } } static gnc_numeric get_numeric_val (gpointer pObject) { slot_info_t* pInfo = (slot_info_t*)pObject; g_return_val_if_fail (pObject != NULL, gnc_numeric_zero ()); if (pInfo->pKvpValue->get_type () == KvpValue::Type::NUMERIC) { return pInfo->pKvpValue->get (); } else { return gnc_numeric_zero (); } } static void set_numeric_val (gpointer pObject, gnc_numeric value) { slot_info_t* pInfo = (slot_info_t*)pObject; g_return_if_fail (pObject != NULL); if (pInfo->value_type != KvpValue::Type::NUMERIC) return; set_slot_from_value (pInfo, new KvpValue {value}); } static GDate* get_gdate_val (gpointer pObject) { slot_info_t* pInfo = (slot_info_t*)pObject; static GDate date; g_return_val_if_fail (pObject != NULL, NULL); if (pInfo->pKvpValue->get_type () == KvpValue::Type::GDATE) { date = pInfo->pKvpValue->get (); return &date; } else { return NULL; } } static void set_gdate_val (gpointer pObject, GDate* value) { slot_info_t* pInfo = (slot_info_t*)pObject; g_return_if_fail (pObject != NULL); if (pInfo->value_type != KvpValue::Type::GDATE) return; set_slot_from_value (pInfo, new KvpValue {*value}); } static slot_info_t* slot_info_copy (slot_info_t* pInfo, GncGUID* guid) { g_return_val_if_fail (pInfo != NULL, NULL); auto newSlot = new slot_info_t; newSlot->be = pInfo->be; newSlot->guid = guid == NULL ? pInfo->guid : guid; newSlot->is_ok = pInfo->is_ok; newSlot->pKvpFrame = pInfo->pKvpFrame; newSlot->value_type = pInfo->value_type; newSlot->pList = pInfo->pList; newSlot->context = pInfo->context; newSlot->pKvpValue = pInfo->pKvpValue; if (!pInfo->path.empty()) newSlot->parent_path = pInfo->path + "/"; else newSlot->parent_path = pInfo->parent_path; return newSlot; } static void save_slot (const char* key, KvpValue* value, slot_info_t & slot_info) { g_return_if_fail (value != NULL); // Ignore if we've already run into a failure if (!slot_info.is_ok) { return; } slot_info.pKvpValue = value; slot_info.path = slot_info.parent_path + key; slot_info.value_type = value->get_type (); switch (slot_info.value_type) { case KvpValue::Type::FRAME: { auto pKvpFrame = value->get (); auto guid = guid_new (); slot_info_t* pNewInfo = slot_info_copy (&slot_info, guid); KvpValue* oldValue = slot_info.pKvpValue; slot_info.pKvpValue = new KvpValue {guid}; slot_info.is_ok = slot_info.be->do_db_operation(OP_DB_INSERT, TABLE_NAME, TABLE_NAME, &slot_info, col_table); g_return_if_fail (slot_info.is_ok); pKvpFrame->for_each_slot_temp (save_slot, *pNewInfo); delete slot_info.pKvpValue; slot_info.pKvpValue = oldValue; delete pNewInfo; } break; case KvpValue::Type::GLIST: { GncGUID* guid = guid_new (); slot_info_t* pNewInfo = slot_info_copy (&slot_info, guid); KvpValue* oldValue = slot_info.pKvpValue; slot_info.pKvpValue = new KvpValue {guid}; // Transfer ownership! slot_info.is_ok = slot_info.be->do_db_operation(OP_DB_INSERT, TABLE_NAME, TABLE_NAME, &slot_info, col_table); g_return_if_fail (slot_info.is_ok); for (auto cursor = value->get (); cursor; cursor = cursor->next) { auto val = static_cast (cursor->data); save_slot ("", val, *pNewInfo); } delete slot_info.pKvpValue; slot_info.pKvpValue = oldValue; delete pNewInfo; } break; default: { slot_info.is_ok = slot_info.be->do_db_operation (OP_DB_INSERT, TABLE_NAME, TABLE_NAME, &slot_info, col_table); } break; } } gboolean gnc_sql_slots_save (GncSqlBackend* sql_be, const GncGUID* guid, gboolean is_infant, QofInstance* inst) { slot_info_t slot_info = { NULL, NULL, TRUE, NULL, KvpValue::Type::INVALID, NULL, FRAME, NULL, "" }; KvpFrame* pFrame = qof_instance_get_slots (inst); g_return_val_if_fail (sql_be != NULL, FALSE); g_return_val_if_fail (guid != NULL, FALSE); g_return_val_if_fail (pFrame != NULL, FALSE); // If this is not saving into a new db, clear out the old saved slots first if (!sql_be->pristine() && !is_infant) { (void)gnc_sql_slots_delete (sql_be, guid); } slot_info.be = sql_be; slot_info.guid = guid; pFrame->for_each_slot_temp (save_slot, slot_info); return slot_info.is_ok; } gboolean gnc_sql_slots_delete (GncSqlBackend* sql_be, const GncGUID* guid) { gchar* buf; gchar guid_buf[GUID_ENCODING_LENGTH + 1]; slot_info_t slot_info = { NULL, NULL, TRUE, NULL, KvpValue::Type::INVALID, NULL, FRAME, NULL, "" }; g_return_val_if_fail (sql_be != NULL, FALSE); g_return_val_if_fail (guid != NULL, FALSE); (void)guid_to_string_buff (guid, guid_buf); buf = g_strdup_printf ("SELECT * FROM %s WHERE obj_guid='%s' and slot_type in ('%d', '%d') and not guid_val is null", TABLE_NAME, guid_buf, KvpValue::Type::FRAME, KvpValue::Type::GLIST); auto stmt = sql_be->create_statement_from_sql(buf); g_free (buf); if (stmt != nullptr) { auto result = sql_be->execute_select_statement(stmt); for (auto row : *result) { const GncSqlColumnTableEntryPtr table_row = col_table[guid_val_col]; GncGUID child_guid; auto val = row.get_string_at_col (table_row->name()); if (val && string_to_guid (val->c_str(), &child_guid)) gnc_sql_slots_delete (sql_be, &child_guid); } } slot_info.be = sql_be; slot_info.guid = guid; slot_info.is_ok = TRUE; slot_info.is_ok = sql_be->do_db_operation(OP_DB_DELETE, TABLE_NAME, TABLE_NAME, &slot_info, obj_guid_col_table); return slot_info.is_ok; } static void load_slot (slot_info_t* pInfo, GncSqlRow& row) { slot_info_t* slot_info; g_return_if_fail (pInfo != NULL); g_return_if_fail (pInfo->be != NULL); g_return_if_fail (pInfo->pKvpFrame != NULL); slot_info = slot_info_copy (pInfo, NULL); gnc_sql_load_object (pInfo->be, row, TABLE_NAME, slot_info, col_table); if (slot_info->pList != pInfo->pList) { if (pInfo->pList != NULL) { PWARN ("Load slot returned a different list than the original"); } else { pInfo->pList = slot_info->pList; } } delete slot_info; } void gnc_sql_slots_load (GncSqlBackend* sql_be, QofInstance* inst) { slot_info_t info = { NULL, NULL, TRUE, NULL, KvpValue::Type::INVALID, NULL, FRAME, NULL, "" }; g_return_if_fail (sql_be != NULL); g_return_if_fail (inst != NULL); info.be = sql_be; info.guid = qof_instance_get_guid (inst); info.pKvpFrame = qof_instance_get_slots (inst); info.context = NONE; slots_load_info (&info); } static void slots_load_info (slot_info_t* pInfo) { g_return_if_fail (pInfo != NULL); g_return_if_fail (pInfo->be != NULL); g_return_if_fail (pInfo->guid != NULL); g_return_if_fail (pInfo->pKvpFrame != NULL); gnc::GUID guid(*pInfo->guid); std::string sql("SELECT * FROM " TABLE_NAME " WHERE obj_guid='"); sql += guid.to_string() + "'"; auto stmt = pInfo->be->create_statement_from_sql(sql); if (stmt != nullptr) { auto result = pInfo->be->execute_select_statement (stmt); for (auto row : *result) load_slot (pInfo, row); delete result; } } static const GncGUID* load_obj_guid (const GncSqlBackend* sql_be, GncSqlRow& row) { static GncGUID guid; g_return_val_if_fail (sql_be != NULL, NULL); gnc_sql_load_object (sql_be, row, NULL, &guid, obj_guid_col_table); return &guid; } static void load_slot_for_book_object (GncSqlBackend* sql_be, GncSqlRow& row, BookLookupFn lookup_fn) { slot_info_t slot_info = { NULL, NULL, TRUE, NULL, KvpValue::Type::INVALID, NULL, FRAME, NULL, "" }; const GncGUID* guid; QofInstance* inst; g_return_if_fail (sql_be != NULL); g_return_if_fail (lookup_fn != NULL); guid = load_obj_guid (sql_be, row); g_return_if_fail (guid != NULL); inst = lookup_fn (guid, sql_be->book()); if (inst == NULL) return; /* Silently bail if the guid isn't loaded yet. */ slot_info.be = sql_be; slot_info.pKvpFrame = qof_instance_get_slots (inst); slot_info.path.clear(); gnc_sql_load_object (sql_be, row, TABLE_NAME, &slot_info, col_table); } /** * gnc_sql_slots_load_for_sql_subquery - Loads slots for all objects whose guid is * supplied by a subquery. The subquery should be of the form "SELECT DISTINCT guid FROM ...". * This is faster than loading for one object at a time because fewer SQL queries * are used. * * @param sql_be SQL backend * @param subquery Subquery SQL string * @param lookup_fn Lookup function */ void gnc_sql_slots_load_for_sql_subquery (GncSqlBackend* sql_be, const std::string subquery, BookLookupFn lookup_fn) { g_return_if_fail (sql_be != NULL); // Ignore empty subquery if (subquery.empty()) return; std::string pkey(obj_guid_col_table[0]->name()); std::string sql("SELECT * FROM " TABLE_NAME " WHERE "); sql += pkey + " IN (" + subquery + ")"; // Execute the query and load the slots auto stmt = sql_be->create_statement_from_sql(sql); if (stmt == nullptr) { PERR ("stmt == NULL, SQL = '%s'\n", sql.c_str()); return; } auto result = sql_be->execute_select_statement(stmt); for (auto row : *result) load_slot_for_book_object (sql_be, row, lookup_fn); delete result; } /* ================================================================= */ void GncSqlSlotsBackend::create_tables (GncSqlBackend* sql_be) { gint version; gboolean ok; g_return_if_fail (sql_be != NULL); version = sql_be->get_table_version( TABLE_NAME); if (version == 0) { (void)sql_be->create_table(TABLE_NAME, TABLE_VERSION, col_table); ok = sql_be->create_index ("slots_guid_index", TABLE_NAME, obj_guid_col_table); if (!ok) { PERR ("Unable to create index\n"); } } else if (version < m_version) { /* Upgrade: 1->2: 64-bit int values to proper definition, add index 2->3: Add gdate field 3->4: Use DATETIME instead of TIMESTAMP in MySQL */ if (version == 1) { sql_be->upgrade_table(TABLE_NAME, col_table); ok = sql_be->create_index ("slots_guid_index", TABLE_NAME, obj_guid_col_table); if (!ok) { PERR ("Unable to create index\n"); } } else if (version == 2) { ok = sql_be->add_columns_to_table(TABLE_NAME, gdate_col_table); if (!ok) { PERR ("Unable to add gdate column\n"); } } else { sql_be->upgrade_table(TABLE_NAME, col_table); } sql_be->set_table_version (TABLE_NAME, TABLE_VERSION); PINFO ("Slots table upgraded from version %d to version %d\n", version, TABLE_VERSION); } } /* ========================== END OF FILE ===================== */