You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gnucash/src/engine/sql/kvp-sql.c

604 lines
16 KiB

/********************************************************************\
* kvp-sql.c : store KVP frames in SQL db *
* Copyright (C) 2001 Linas Vepstas <linas@linas.org> *
* *
* 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 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
\********************************************************************/
/*
* FILE:
* kvp-sql.c
*
* FUNCTION:
* save & restore of KVP frames
*
* HISTORY:
* Copyright (c) 2001 Linas Vepstas
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include "kvp-sql.h"
#include "PostgresBackend.h"
#include "putil.h"
static short module = MOD_KVP;
/* =========================================================== */
/* given integer ipath (path id) and a string, poke the string
* into a cache in local memory
*/
static void
pgendPokePathCache (PGBackend *be, int ipath, const char *path_str)
{
int i;
/* get more memory for cache if needed */
if (ipath >= be->path_cache_size)
{
be->path_cache =
(char **) g_realloc (be->path_cache, (ipath+100)*sizeof (char *));
for (i=be->path_cache_size; i<ipath+100; i++) {
(be->path_cache)[i] = NULL;
}
be->path_cache_size = ipath+100;
}
/* poke string into slot ipath */
if (NULL == (be->path_cache)[ipath])
{
(be->path_cache)[ipath] = g_strdup (path_str);
}
if (be->ipath_max < ipath) be->ipath_max = ipath;
}
/* =========================================================== */
/* given an integer ipath (path id), return the path string
* from local memory
*/
static char *
pgendPeekPathCache (PGBackend *be, int ipath)
{
if (ipath > be->ipath_max) return NULL;
if (0 >= ipath) return NULL;
return (be->path_cache)[ipath];
}
/* =========================================================== */
/* utility function, used to access the two cache tables */
static gpointer
ival_cb (PGBackend *be, PGresult *result, int j, gpointer data)
{
int ival = atoi (DB_GET_VAL ((char *)data, 0));
return (gpointer) ival;
}
static int
pgendGetCache (PGBackend *be,
const char *table_name,
const char *key_name,
const char *cache_name,
const char *val_str)
{
char *p;
int ival =0;
if (!be || !val_str) return 0;
/* first, lets see if we can find the guid.
* If we can then just return it */
p = be->buff; *p = 0;
p = stpcpy (p, "SELECT ");
p = stpcpy (p, cache_name);
p = stpcpy (p, " FROM ");
p = stpcpy (p, table_name);
p = stpcpy (p, " WHERE ");
p = stpcpy (p, key_name);
p = stpcpy (p, " ='");
p = stpcpy (p, val_str);
p = stpcpy (p, "';");
SEND_QUERY (be,be->buff, 0);
ival = (int) pgendGetResults (be, ival_cb, (gpointer) cache_name);
if (ival && (ival != (int)cache_name)) return ival;
/* Else, this guid has never been stored before.
* Poke it into the the database */
p = be->buff; *p = 0;
p = stpcpy (p, "INSERT INTO ");
p = stpcpy (p, table_name);
p = stpcpy (p, " (");
p = stpcpy (p, key_name);
p = stpcpy (p, ") VALUES ('");
p = stpcpy (p, val_str);
p = stpcpy (p, "');");
SEND_QUERY (be,be->buff, 0);
FINISH_QUERY(be->connection);
/* and requery to get the serial number ... */
ival = pgendGetCache (be, table_name, key_name, cache_name, val_str);
return ival;
}
/* =========================================================== */
/* given a string, return the corresponding int from the sql db. */
static int
pgendGetPathCache (PGBackend *be, const char *path_str)
{
int ival;
ival = pgendGetCache (be, "gncPathCache", "path", "ipath", path_str);
PINFO ("cached %d for %s", ival, path_str ? path_str : "(null)");
if (0 >= ival) return ival;
pgendPokePathCache (be, ival, path_str);
return ival;
}
/* =========================================================== */
/* given a string, return the corresponding int from the sql db. */
static int
pgendGetGUIDCacheIDStr (PGBackend *be, const char *guid_str)
{
int ival;
ival = pgendGetCache (be, "gncGUIDCache", "guid", "iguid", guid_str);
PINFO ("cached %d for %s", ival, guid_str ? guid_str : "(null)");
return ival;
}
/* =========================================================== */
/* given a guid, return the corresponding int from the sql db. */
static int
pgendGetGUIDCacheID (PGBackend *be, const GUID *guid)
{
char guid_str[GUID_ENCODING_LENGTH+1];
if (!be || !guid_str) return 0;
guid_to_string_buff (guid, guid_str);
return pgendGetGUIDCacheIDStr (be, guid_str);
}
/* =========================================================== */
/* storage of the kvp data to the database is done with the aid
* of a traversal callback. The store_cb() routine is the callback.
*/
typedef struct store_data_s {
PGBackend *be;
int iguid;
int ipath;
char *path;
char *stype;
union {
gint64 ival;
double dbl;
gnc_numeric numeric;
const char *str;
const GUID *guid;
GList *list;
} u;
} store_data_t;
#include "kvp-autogen.c"
static void
store_cb (const char *key, kvp_value *val, gpointer p)
{
store_data_t *cb_data = (store_data_t *) p;
PGBackend *be = cb_data->be;
int ipath;
char *path_save;
path_save = cb_data->path;
cb_data->path = g_strjoin ("/", path_save, key, 0);
ipath = pgendGetPathCache (be, cb_data->path);
cb_data->ipath = ipath;
if (ipath)
{
switch (kvp_value_get_type (val))
{
case KVP_TYPE_GINT64:
{
gint64 ival = kvp_value_get_gint64 (val);
PINFO ("path=%s type=gint64 val=%lld",
cb_data->path,
(long long int) ival);
cb_data->stype = "int8";
cb_data->u.ival = ival;
pgendPutOneKVPint64Only (be, cb_data);
}
break;
case KVP_TYPE_DOUBLE:
{
double ival = kvp_value_get_double (val);
PINFO ("path=%s type=double val=%g", cb_data->path, ival);
cb_data->stype = "flt8";
cb_data->u.dbl = ival;
pgendPutOneKVPdoubleOnly (be, cb_data);
}
break;
case KVP_TYPE_NUMERIC:
{
gnc_numeric ival = kvp_value_get_numeric (val);
PINFO ("path=%s type=numeric val=%lld/%lld",
cb_data->path,
(long long int) ival.num,
(long long int) ival.denom);
cb_data->stype = "frac";
cb_data->u.numeric = ival;
pgendPutOneKVPnumericOnly (be, cb_data);
}
break;
case KVP_TYPE_STRING:
{
const char *str = kvp_value_get_string (val);
PINFO ("path=%s type=str val=%s", cb_data->path, str);
cb_data->stype = "text";
cb_data->u.str = str;
pgendPutOneKVPstringOnly (be, cb_data);
}
break;
case KVP_TYPE_GUID:
{
char guid_str[GUID_ENCODING_LENGTH+1];
const GUID *guid = kvp_value_get_guid(val);
guid_to_string_buff (guid, guid_str);
PINFO ("path=%s type=guid val=%s", cb_data->path, guid_str);
cb_data->stype = "guid";
cb_data->u.str = guid_str;
pgendPutOneKVPguidOnly (be, cb_data);
}
break;
case KVP_TYPE_BINARY:
PERR ("Binary KVP Type not yet implemented\n");
break;
case KVP_TYPE_GLIST:
{
GList *start;
start = kvp_value_get_glist (val);
PINFO ("path=%s type=glist", cb_data->path);
cb_data->stype = "list";
cb_data->u.list = start;
PERR ("List KVP Type not yet implemented\n");
}
break;
case KVP_TYPE_FRAME:
{
kvp_frame *frame;
PINFO ("path=%s type=frame", cb_data->path);
frame = kvp_value_get_frame (val);
kvp_frame_for_each_slot (frame, store_cb, p);
}
break;
default:
PERR("Unknown type %d for path=%s\n",
kvp_value_get_type (val), cb_data->path);
}
}
g_free (cb_data->path);
cb_data->path = path_save;
}
void
pgendKVPStore (PGBackend *be, const GUID *guid, kvp_frame *kf)
{
store_data_t cb_data;
int iguid;
if (!be || !guid || !kf) return;
ENTER (" ");
iguid = pgendGetGUIDCacheID (be, guid);
if (0 == iguid) return;
cb_data.be = be;
cb_data.iguid = iguid;
cb_data.path = "";
kvp_frame_for_each_slot (kf, store_cb, &cb_data);
LEAVE (" ");
}
/* =========================================================== */
/* These functions suck new, unknown paths out of the database
* and poke them into our local cache.
*/
static gpointer
path_loader (PGBackend *be, PGresult *result, int j, gpointer data)
{
int ipath = atoi (DB_GET_VAL ("ipath", j));
char *path = DB_GET_VAL ("path", j);
pgendPokePathCache (be, ipath, path);
return 0;
}
void
pgendKVPInit (PGBackend *be)
{
char *p;
/* don't re-init multiple times in single-user mode.
* Once is enough. But in multi-user mode, we need to
* check constantly, since other users may have added
* more paths.
*/
if (((MODE_SINGLE_UPDATE == be->session_mode) ||
(MODE_SINGLE_FILE == be->session_mode)) &&
(0 < be->ipath_max)) return;
/* get new paths out of the database */
p = be->buff; *p=0;
p = stpcpy (p, "SELECT * FROM gncPathCache WHERE ipath > ");
p += sprintf (p, "%d", be->ipath_max);
p = stpcpy (p, ";");
SEND_QUERY (be,be->buff, );
pgendGetResults (be, path_loader, NULL);
}
/* =========================================================== */
/* hack alert -- this code assumed that the path cache
* is up to date, which it might not be in a multi-user world
*/
#define KVP_HANDLER_SETUP \
kvp_frame *kf = (kvp_frame *) data; \
kvp_frame *final; \
kvp_value * kv=NULL; \
char *path, *tail; \
int ipath; \
\
ipath = atoi (DB_GET_VAL ("ipath", j)); \
path = pgendPeekPathCache (be, ipath); \
if (!path) return kf; /* should never happen */ \
tail = strrchr (path, '/'); \
*tail = 0x0; \
tail ++; \
\
if (!kf) kf = kvp_frame_new(); \
#define KVP_HANDLER_TAKEDOWN \
final = kvp_frame_get_frame_slash (kf, path); \
kvp_frame_set_slot_nc (final, tail, kv); \
\
/* put the slash back */ \
tail --; \
*tail = '/'; \
\
return kf; \
static gpointer
int64_handler (PGBackend *be, PGresult *result, int j, gpointer data)
{
KVP_HANDLER_SETUP;
kv = kvp_value_new_gint64 (atoll (DB_GET_VAL ("data", j)));
KVP_HANDLER_TAKEDOWN;
}
static gpointer
dbl_handler (PGBackend *be, PGresult *result, int j, gpointer data)
{
KVP_HANDLER_SETUP;
kv = kvp_value_new_double (atof (DB_GET_VAL ("data", j)));
KVP_HANDLER_TAKEDOWN;
}
static gpointer
numeric_handler (PGBackend *be, PGresult *result, int j, gpointer data)
{
gnc_numeric gn;
KVP_HANDLER_SETUP;
gn.num = atoll (DB_GET_VAL ("num", j));
gn.denom = atoll (DB_GET_VAL ("denom", j));
kv = kvp_value_new_gnc_numeric (gn);
KVP_HANDLER_TAKEDOWN;
}
static gpointer
str_handler (PGBackend *be, PGresult *result, int j, gpointer data)
{
KVP_HANDLER_SETUP;
kv = kvp_value_new_string (DB_GET_VAL ("data", j));
KVP_HANDLER_TAKEDOWN;
}
static gpointer
guid_handler (PGBackend *be, PGresult *result, int j, gpointer data)
{
gboolean rc;
GUID guid;
KVP_HANDLER_SETUP;
rc = string_to_guid ((DB_GET_VAL ("data", j)), &guid);
if (rc) kv = kvp_value_new_guid (&guid);
KVP_HANDLER_TAKEDOWN;
}
static gpointer
list_handler (PGBackend *be, PGresult *result, int j, gpointer data)
{
KVP_HANDLER_SETUP;
PERR ("not implemented");
// kv = kvp_value_new_glist ();
KVP_HANDLER_TAKEDOWN;
}
#define GET_KVP(TYPE) \
{ \
p = be->buff; *p = 0; \
p = stpcpy (p, "SELECT * FROM gncKVPValue_" #TYPE " WHERE iguid="); \
p = stpcpy (p, iguid_str); \
\
SEND_QUERY (be,be->buff, kf); \
kf = pgendGetResults (be, TYPE##_handler, kf); \
}
static gpointer
count_handler (PGBackend *be, PGresult *result, int j, gpointer data)
{
int *cnt = (int *) data;
*cnt += atoi (DB_GET_VAL ("count", j));
return data;
}
kvp_frame *
pgendKVPFetch (PGBackend *be, const GUID *guid, kvp_frame *kf)
{
char * p;
char iguid_str[40];
int iguid = 0;
int count = 0;
if (!be || !guid) return kf;
ENTER (" ");
/* update the path cache; other users may have added more paths */
pgendKVPInit (be);
/* get the effective iguid for this object */
iguid = pgendGetGUIDCacheID (be, guid);
if (0 == iguid) return kf;
snprintf (iguid_str, 40, "%d;", iguid);
/* save on some sql queries by avoiding kvp data fetches when
* tehre is no data */
p = be->buff; *p = 0;
p = stpcpy (p, "SELECT count(*) FROM gncKVPValue WHERE iguid=");
p = stpcpy (p, iguid_str);
SEND_QUERY (be,be->buff, kf);
pgendGetResults (be, count_handler, &count);
if (0 == count) return kf;
/* now troll the individual tables for data */
GET_KVP(int64);
GET_KVP(dbl);
GET_KVP(numeric);
GET_KVP(str);
GET_KVP(guid);
GET_KVP(list);
LEAVE (" ");
return kf;
}
/* =========================================================== */
#define CPY_KVP(TYPE) \
{ \
p = stpcpy (p, "INSERT INTO gncKVPValue" TYPE "Trail SELECT '"); \
p = stpcpy (p, sess_str); \
p = stpcpy (p, "' as sessionGuid, datetime('NOW') as date_changed, " \
"'d' as change, * from gncKVPValue" TYPE " WHERE iguid=");\
p = stpcpy (p, iguid_str); \
}
void
pgendKVPDeleteStr (PGBackend *be, const char *guid)
{
char iguid_str[80], sess_str[80];
char * p;
int iguid = 0;
if (!be || !guid) return;
iguid = pgendGetGUIDCacheIDStr (be, guid);
if (0 == iguid) return;
sprintf (iguid_str, "%d;\n", iguid);
guid_to_string_buff (be->sessionGuid, sess_str);
/* first, copy values to the audit tables */
p = be->buff; *p = 0;
CPY_KVP("");
CPY_KVP("_dbl");
CPY_KVP("_guid");
CPY_KVP("_int64");
CPY_KVP("_list");
CPY_KVP("_numeric");
CPY_KVP("_str");
/* then delete the values */
p = stpcpy (p, "DELETE FROM gncKVPValue WHERE iguid=");
p = stpcpy (p, iguid_str);
p = stpcpy (p, "DELETE FROM gncKVPValue_dbl WHERE iguid=");
p = stpcpy (p, iguid_str);
p = stpcpy (p, "DELETE FROM gncKVPValue_guid WHERE iguid=");
p = stpcpy (p, iguid_str);
p = stpcpy (p, "DELETE FROM gncKVPValue_int64 WHERE iguid=");
p = stpcpy (p, iguid_str);
p = stpcpy (p, "DELETE FROM gncKVPValue_list WHERE iguid=");
p = stpcpy (p, iguid_str);
p = stpcpy (p, "DELETE FROM gncKVPValue_numeric WHERE iguid=");
p = stpcpy (p, iguid_str);
p = stpcpy (p, "DELETE FROM gncKVPValue_str WHERE iguid=");
p = stpcpy (p, iguid_str);
SEND_QUERY (be,be->buff, );
FINISH_QUERY(be->connection);
}
/* =========================================================== */
void
pgendKVPDelete (PGBackend *be, const GUID *guid)
{
char guid_str[33];
if (!be || !guid) return;
guid_to_string_buff (guid, guid_str);
return pgendKVPDeleteStr (be, guid_str);
}
/* =========================== END OF FILE ===================== */