~siretart/gnucash/ubuntu-fullsource

« back to all changes in this revision

Viewing changes to src/backend/postgres/price.c

  • Committer: Reinhard Tartler
  • Date: 2008-08-03 07:25:46 UTC
  • Revision ID: siretart@tauware.de-20080803072546-y6p8xda8zpfi62ys
import gnucash_2.2.4.orig.tar.gz

The original tarball had the md5sum: 27e660297dc5b8ce574515779d05a5a5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/********************************************************************\
 
2
 * price.c -- implements price handling for the postgres backend    *
 
3
 * Copyright (c) 2001, 2002 Linas Vepstas <linas@linas.org>         *
 
4
 *                                                                  *
 
5
 * This program is free software; you can redistribute it and/or    *
 
6
 * modify it under the terms of the GNU General Public License as   *
 
7
 * published by the Free Software Foundation; either version 2 of   *
 
8
 * the License, or (at your option) any later version.              *
 
9
 *                                                                  *
 
10
 * This program is distributed in the hope that it will be useful,  *
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
 
13
 * GNU General Public License for more details.                     *
 
14
 *                                                                  *
 
15
 * You should have received a copy of the GNU General Public License*
 
16
 * along with this program; if not, contact:                        *
 
17
 *                                                                  *
 
18
 * Free Software Foundation           Voice:  +1-617-542-5942       *
 
19
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
 
20
 * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
 
21
\********************************************************************/
 
22
 
 
23
#include "config.h"
 
24
 
 
25
#include <glib.h>
 
26
#include <stdlib.h>
 
27
#include <string.h>
 
28
#include <libpq-fe.h>  
 
29
 
 
30
#include "qof.h"
 
31
#include "gnc-commodity.h"
 
32
#include "gnc-engine.h"
 
33
#include "gnc-pricedb.h"
 
34
#include "gnc-pricedb-p.h"
 
35
 
 
36
#include "PostgresBackend.h"
 
37
#include "base-autogen.h"
 
38
#include "escape.h"
 
39
#include "price.h"
 
40
#include "putil.h"
 
41
 
 
42
static QofLogModule log_module = GNC_MOD_BACKEND;
 
43
 
 
44
/* ============================================================= */
 
45
/* ============================================================= */
 
46
/*                COMMODITIES  STUFF                             */
 
47
/* ============================================================= */
 
48
/* ============================================================= */
 
49
 
 
50
/* This routine restores all commodities in the database.
 
51
 */
 
52
 
 
53
static gpointer
 
54
get_commodities_cb (PGBackend *be, PGresult *result, int j, gpointer data)
 
55
{
 
56
   GList *node;
 
57
 
 
58
   /* stick the commodity in every book ... */
 
59
   for (node=be->blist; node; node=node->next)
 
60
   {
 
61
      gnc_commodity *com;
 
62
      QofBook *book = node->data;
 
63
      gnc_commodity_table *comtab = gnc_book_get_commodity_table (book);
 
64
   
 
65
      if (!comtab) continue;
 
66
 
 
67
      /* first, lets see if we've already got this one */
 
68
      com = gnc_commodity_table_lookup(comtab,
 
69
                                       DB_GET_VAL("namespace",j),
 
70
                                       DB_GET_VAL("mnemonic",j));
 
71
   
 
72
      if (com) continue;
 
73
   
 
74
      /* no we don't ... restore it */
 
75
      com = gnc_commodity_new (book,
 
76
                               DB_GET_VAL("fullname",j),
 
77
                               DB_GET_VAL("namespace",j),
 
78
                               DB_GET_VAL("mnemonic",j),
 
79
                               DB_GET_VAL("code",j),
 
80
                               atoi(DB_GET_VAL("fraction",j)));
 
81
   
 
82
      gnc_commodity_table_insert (comtab, com);
 
83
   }
 
84
   return NULL;
 
85
}
 
86
 
 
87
void
 
88
pgendGetAllCommodities (PGBackend *be)
 
89
{
 
90
   char * p;
 
91
   if (!be) return;
 
92
 
 
93
   ENTER ("be=%p, conn=%p", be, be->connection);
 
94
 
 
95
   /* Get them ALL */
 
96
   p = "SELECT * FROM gncCommodity;";
 
97
   SEND_QUERY (be, p, );
 
98
   pgendGetResults (be, get_commodities_cb, NULL);
 
99
 
 
100
   LEAVE (" ");
 
101
}
 
102
 
 
103
void
 
104
pgendGetCommodity (PGBackend *be, const char * unique_name)
 
105
{
 
106
   sqlEscape *escape;
 
107
   char *p;
 
108
 
 
109
   if (!be || !unique_name) return;
 
110
 
 
111
   ENTER ("be=%p, conn=%p", be, be->connection);
 
112
 
 
113
   escape = sqlEscape_new ();
 
114
 
 
115
   /* Get them ALL */
 
116
   p = be->buff;
 
117
   p = stpcpy (p, "SELECT * FROM gncCommodity WHERE gncCommodity.commodity='");
 
118
   p = stpcpy (p, sqlEscapeString (escape, unique_name));
 
119
   p = stpcpy (p, "';");
 
120
 
 
121
   SEND_QUERY (be, be->buff, );
 
122
   pgendGetResults (be, get_commodities_cb, NULL);
 
123
 
 
124
   sqlEscape_destroy (escape);
 
125
 
 
126
   LEAVE (" ");
 
127
}
 
128
 
 
129
/* ============================================================= */
 
130
/* ============================================================= */
 
131
/*                PRICE  STUFF                                   */
 
132
/* ============================================================= */
 
133
/* ============================================================= */
 
134
/* store just one price */
 
135
 
 
136
static void
 
137
pgendStorePriceNoLock (PGBackend *be, GNCPrice *pr,
 
138
                        gboolean do_check_version)
 
139
{
 
140
   gnc_commodity *modity;
 
141
 
 
142
   if (do_check_version)
 
143
   {
 
144
     if (0 < pgendPriceCompareVersion (be, pr)) return;
 
145
   }
 
146
   /* be sure to update the version !! */
 
147
   qof_instance_increment_version(pr, be->version_check);
 
148
 
 
149
   /* make sure that we've stored the commodity 
 
150
    * and currency before we store the price.
 
151
    */
 
152
   modity = gnc_price_get_commodity (pr);
 
153
   pgendPutOneCommodityOnly (be, modity);
 
154
 
 
155
   modity = gnc_price_get_currency (pr);
 
156
   pgendPutOneCommodityOnly (be, modity);
 
157
 
 
158
   pgendPutOnePriceOnly (be, pr);
 
159
}
 
160
 
 
161
/* ============================================================= */
 
162
/* store entire price database */
 
163
 
 
164
static gboolean 
 
165
foreach_price_cb (GNCPrice *pr, gpointer bend)
 
166
{
 
167
   PGBackend *be = (PGBackend *) bend;
 
168
   gnc_commodity *modity;
 
169
   gint16 mark;
 
170
 
 
171
   /* make sure that we've stored the commodity 
 
172
    * and currency before we store the price.
 
173
    * We use marks to avoid redundant stores. 
 
174
    */
 
175
   modity = gnc_price_get_commodity (pr);
 
176
   mark = gnc_commodity_get_mark (modity);
 
177
   if (!mark) {
 
178
      pgendPutOneCommodityOnly (be, modity);
 
179
      gnc_commodity_set_mark (modity, 1);
 
180
   }
 
181
 
 
182
   modity = gnc_price_get_currency (pr);
 
183
   mark = gnc_commodity_get_mark (modity);
 
184
   if (!mark) {
 
185
      pgendPutOneCommodityOnly (be, modity);
 
186
      gnc_commodity_set_mark (modity, 1);
 
187
   }
 
188
 
 
189
   pgendPutOnePriceOnly (be, pr);
 
190
 
 
191
   return TRUE;
 
192
}
 
193
 
 
194
static gboolean
 
195
commodity_mark_cb (gnc_commodity *cm, gpointer user_data)
 
196
{
 
197
   gint32 v = ((gint32) GPOINTER_TO_INT(user_data)) & 0xffff;
 
198
   gnc_commodity_set_mark (cm, (gint16) v);
 
199
   return TRUE;
 
200
}
 
201
 
 
202
 
 
203
void
 
204
pgendStorePriceDBNoLock (PGBackend *be, QofBook *book)
 
205
{
 
206
   GNCPriceDB *prdb;
 
207
   gnc_commodity_table *comtab;
 
208
 
 
209
   prdb = gnc_book_get_pricedb(book);
 
210
   comtab = gnc_book_get_commodity_table (book);
 
211
 
 
212
   /* Clear the marks on commodities -- we use this to mark 
 
213
    * the thing as 'already stored', avoiding redundant stores */
 
214
   gnc_commodity_table_foreach_commodity (comtab, commodity_mark_cb, 0);
 
215
 
 
216
   gnc_pricedb_foreach_price (prdb, foreach_price_cb,
 
217
                              (gpointer) be, FALSE);
 
218
 
 
219
   gnc_commodity_table_foreach_commodity (comtab, commodity_mark_cb, 0);
 
220
}
 
221
 
 
222
void
 
223
pgendStorePriceDB (PGBackend *be, QofBook *book)
 
224
{
 
225
   char *p;
 
226
   ENTER ("be=%p, book=%p", be, book);
 
227
   if (!be || !book) return;
 
228
 
 
229
   /* Lock it up so that we store atomically */
 
230
   p = "BEGIN;\n"
 
231
       "LOCK TABLE gncPrice IN EXCLUSIVE MODE;\n";
 
232
   SEND_QUERY (be,p, );
 
233
   FINISH_QUERY(be->connection);
 
234
 
 
235
   pgendStorePriceDBNoLock (be, book);
 
236
 
 
237
   p = "COMMIT;\n"
 
238
       "NOTIFY gncPrice;";
 
239
   SEND_QUERY (be,p, );
 
240
   FINISH_QUERY(be->connection);
 
241
   LEAVE(" ");
 
242
}
 
243
 
 
244
/* ============================================================= */
 
245
/* The pgendGetAllPrices() routine sucks *all* of the 
 
246
 *    prices out of the database.  This is a potential 
 
247
 *    CPU and memory-burner; its use is not suggested for anything
 
248
 *    but single-user mode.
 
249
 */
 
250
 
 
251
static gpointer
 
252
get_price_cb (PGBackend *be, PGresult *result, int j, gpointer data)
 
253
{
 
254
   QofBook *book = data;
 
255
   GNCPriceDB *prdb;
 
256
   GNCPrice *pr;
 
257
   gint32 sql_vers, local_vers;
 
258
   Timespec ts;
 
259
   gint64 num, denom;
 
260
   gnc_numeric value;
 
261
   GUID guid = nullguid;
 
262
   int not_found = 0;
 
263
 
 
264
   gnc_commodity * modity;
 
265
 
 
266
   FIND_BOOK (book);
 
267
 
 
268
   prdb = gnc_book_get_pricedb(book);
 
269
 
 
270
   /* First, lets see if we've already got this one */
 
271
   string_to_guid (DB_GET_VAL ("priceGuid", j), &guid);
 
272
   pr = gnc_price_lookup (&guid, book);
 
273
 
 
274
   if (!pr) 
 
275
   { 
 
276
      pr = gnc_price_create(book);
 
277
      gnc_price_begin_edit (pr);
 
278
      gnc_price_set_guid (pr, &guid);
 
279
      not_found = 1;
 
280
   } 
 
281
   else
 
282
   {
 
283
      gnc_price_ref (pr);
 
284
      gnc_price_begin_edit (pr);
 
285
      not_found = 0;
 
286
   }
 
287
 
 
288
   /* compare versions. Hack alert -- Not sure how to handle failures */
 
289
   sql_vers = atoi (DB_GET_VAL("version",j));
 
290
   local_vers = qof_instance_get_version(pr);
 
291
   if (sql_vers < local_vers) {
 
292
      PERR ("local price version is higher than db !!! local=%d sql=%d",
 
293
         local_vers, sql_vers);
 
294
      gnc_price_commit_edit (pr);
 
295
      gnc_price_unref (pr);
 
296
      return data;
 
297
   }
 
298
   qof_instance_set_version (pr, sql_vers);
 
299
 
 
300
   modity = gnc_string_to_commodity (DB_GET_VAL("commodity",j), book);
 
301
   gnc_price_set_commodity (pr, modity);
 
302
 
 
303
   modity = gnc_string_to_commodity (DB_GET_VAL("currency",j), book);
 
304
   gnc_price_set_currency (pr, modity);
 
305
 
 
306
   ts = gnc_iso8601_to_timespec_gmt (DB_GET_VAL("time",j));
 
307
   gnc_price_set_time (pr, ts);
 
308
 
 
309
   gnc_price_set_source (pr, DB_GET_VAL("source",j));
 
310
   gnc_price_set_typestr (pr, DB_GET_VAL("type",j));
 
311
 
 
312
   num = strtoll (DB_GET_VAL("valueNum", j), NULL, 0);
 
313
   denom = strtoll (DB_GET_VAL("valueDenom", j), NULL, 0);
 
314
   value = gnc_numeric_create (num, denom);
 
315
   gnc_price_set_value (pr, value);
 
316
 
 
317
   if (not_found) gnc_pricedb_add_price(prdb, pr);
 
318
   gnc_price_commit_edit (pr);
 
319
   gnc_price_unref (pr);
 
320
 
 
321
   return data;
 
322
}
 
323
 
 
324
 
 
325
void
 
326
pgendGetAllPricesInBook (PGBackend *be, QofBook *book)
 
327
{
 
328
   char buff[400], *p;
 
329
 
 
330
   if (!be) return;
 
331
   ENTER ("be=%p, conn=%p", be, be->connection);
 
332
 
 
333
   /* first, make sure commodities table is up to date */
 
334
   pgendGetAllCommodities (be);
 
335
 
 
336
   /* Get them ALL */
 
337
   p = buff;
 
338
   p = stpcpy (p, "SELECT * FROM gncPrice WHERE bookGuid='");
 
339
   p = guid_to_string_buff (qof_book_get_guid(book), p);
 
340
   p = stpcpy (p, "';");
 
341
   SEND_QUERY (be, buff, );
 
342
   pgendGetResults (be, get_price_cb, book);
 
343
 
 
344
   LEAVE (" ");
 
345
}
 
346
 
 
347
/* ============================================================= */
 
348
 
 
349
void
 
350
pgendPriceFind (QofBackend *bend, gpointer olook)
 
351
{
 
352
   PGBackend *be = (PGBackend *)bend;
 
353
   GNCPriceLookup *look = (GNCPriceLookup *)olook;
 
354
   const char * commodity_str;
 
355
   const char * currency_str;
 
356
   sqlEscape *escape;
 
357
   char * p;
 
358
 
 
359
   ENTER ("be=%p, lookup=%p", be, look);
 
360
   if (!be || !look) { LEAVE("(null) args"); return; }
 
361
 
 
362
   /* special case the two-way search in terms of more basic primitives */
 
363
   if (LOOKUP_NEAREST_IN_TIME == look->type)
 
364
   {
 
365
      look->type = LOOKUP_LATEST_BEFORE;
 
366
      pgendPriceFind (bend, look);
 
367
      look->type = LOOKUP_EARLIEST_AFTER;
 
368
      pgendPriceFind (bend, look);
 
369
      LEAVE(" ");
 
370
      return;
 
371
   }
 
372
 
 
373
   escape = sqlEscape_new ();
 
374
 
 
375
   commodity_str = gnc_commodity_get_unique_name(look->commodity);
 
376
   currency_str  = gnc_commodity_get_unique_name(look->currency);
 
377
 
 
378
   /* don't send events  to GUI, don't accept callbacks to backend */
 
379
   qof_event_suspend();
 
380
   pgendDisable(be);
 
381
 
 
382
   /* set up the common part of the query */
 
383
   p = be->buff; *p = 0;
 
384
   p = stpcpy (p, "SELECT * FROM gncPrice"
 
385
                  "  WHERE commodity='");
 
386
   p = stpcpy (p, sqlEscapeString (escape, commodity_str));
 
387
   p = stpcpy (p, "' ");
 
388
 
 
389
   if (currency_str) {
 
390
       p = stpcpy (p, "AND currency='");
 
391
       p = stpcpy (p, sqlEscapeString (escape, currency_str));
 
392
       p = stpcpy (p, "' ");
 
393
   }
 
394
 
 
395
   PINFO("query = %s", be->buff);
 
396
 
 
397
   sqlEscape_destroy (escape);
 
398
   escape = NULL;
 
399
 
 
400
   switch (look->type)
 
401
   {
 
402
      case LOOKUP_LATEST:
 
403
         p = stpcpy (p, "ORDER BY time DESC LIMIT 1;");
 
404
         break;
 
405
      case LOOKUP_ALL:
 
406
         /* Get all prices for this commodity and currency */
 
407
         p = stpcpy (p, ";");
 
408
         break;
 
409
      case LOOKUP_AT_TIME:
 
410
         p = stpcpy (p, "AND time='");
 
411
         p = gnc_timespec_to_iso8601_buff (look->date, p);
 
412
         p = stpcpy (p, "';");
 
413
         break;
 
414
      case LOOKUP_NEAREST_IN_TIME:
 
415
         PERR ("this can't possibly happen but it did!!!");
 
416
         p = stpcpy (p, ";");
 
417
         break;
 
418
      case LOOKUP_LATEST_BEFORE:
 
419
         p = stpcpy (p, "AND time <= '");
 
420
         p = gnc_timespec_to_iso8601_buff (look->date, p);
 
421
         p = stpcpy (p, "' ORDER BY time DESC LIMIT 1;");
 
422
         break;
 
423
      case LOOKUP_EARLIEST_AFTER:
 
424
         p = stpcpy (p, "AND time >= '");
 
425
         p = gnc_timespec_to_iso8601_buff (look->date, p);
 
426
         p = stpcpy (p, "' ORDER BY time ASC LIMIT 1;");
 
427
         break;
 
428
      default:
 
429
         PERR ("unknown lookup type %d", look->type);
 
430
         /* re-enable events */
 
431
         pgendEnable(be);
 
432
         qof_event_resume();
 
433
         LEAVE(" ");
 
434
         return;
 
435
   }
 
436
 
 
437
   SEND_QUERY (be, be->buff, );
 
438
   pgendGetResults (be, get_price_cb, NULL);
 
439
 
 
440
   /* re-enable events */
 
441
   pgendEnable(be);
 
442
   qof_event_resume();
 
443
   LEAVE(" ");
 
444
}
 
445
 
 
446
/* ============================================================= */
 
447
/* ============================================================= */
 
448
/*         HIGHER LEVEL ROUTINES AND BACKEND PROPER              */
 
449
/* ============================================================= */
 
450
/* ============================================================= */
 
451
 
 
452
void
 
453
pgend_price_begin_edit (QofBackend * bend, GNCPrice *pr)
 
454
{
 
455
   if (pr && pr->db && qof_instance_get_dirty_flag(pr->db)) 
 
456
   {
 
457
      PERR ("price db is unexpectedly dirty");
 
458
   }
 
459
   return;
 
460
}
 
461
 
 
462
void
 
463
pgend_price_commit_edit (QofBackend * bend, GNCPrice *pr)
 
464
{
 
465
   char * bufp;
 
466
   PGBackend *be = (PGBackend *)bend;
 
467
 
 
468
   ENTER ("be=%p, price=%p", be, pr);
 
469
   if (!be || !pr) return; 
 
470
 
 
471
   /* lock it up so that we query and store atomically */
 
472
   bufp = "BEGIN;\n"
 
473
          "LOCK TABLE gncPrice IN EXCLUSIVE MODE;\n";
 
474
   SEND_QUERY (be,bufp,);
 
475
   FINISH_QUERY(be->connection);
 
476
 
 
477
   /* check to see that the engine version is equal or newer than 
 
478
    * whats in the database.  It its not, then some other user has 
 
479
    * made changes, and we must roll back. */
 
480
   if (0 < pgendPriceCompareVersion (be, pr))
 
481
   {
 
482
      qof_instance_set_destroying(pr, FALSE);
 
483
      bufp = "ROLLBACK;";
 
484
      SEND_QUERY (be,bufp,);
 
485
      FINISH_QUERY(be->connection);
 
486
 
 
487
      /* hack alert -- we should restore the price data from the 
 
488
       * sql back end at this point ! !!! */
 
489
      PWARN(" price data in engine is newer\n"
 
490
            " price must be rolled back.  This function\n"
 
491
            " is not completely implemented !! \n");
 
492
      LEAVE ("rolled back");
 
493
      qof_backend_set_error (&be->be, ERR_BACKEND_MODIFIED);
 
494
      return;
 
495
   }
 
496
   /* be sure to update the version !! */
 
497
   qof_instance_increment_version(pr, be->version_check);
 
498
 
 
499
   if (qof_instance_get_destroying(pr))
 
500
   {
 
501
      pgendStoreAuditPrice (be, pr, SQL_DELETE);
 
502
      bufp = be->buff; *bufp = 0;
 
503
      bufp = stpcpy (bufp, "DELETE FROM gncPrice WHERE priceGuid='");
 
504
      bufp = guid_to_string_buff (gnc_price_get_guid(pr), bufp);
 
505
      bufp = stpcpy (bufp, "';");
 
506
      PINFO ("%s\n", be->buff ? be->buff : "(null)");
 
507
      SEND_QUERY (be,be->buff, );
 
508
      FINISH_QUERY(be->connection);
 
509
   }
 
510
   else 
 
511
   { 
 
512
      pgendStorePriceNoLock (be, pr, FALSE);
 
513
   }
 
514
 
 
515
   bufp = "COMMIT;\n"
 
516
          "NOTIFY gncPrice;";
 
517
   SEND_QUERY (be,bufp,);
 
518
   FINISH_QUERY(be->connection);
 
519
 
 
520
   if (pr->db)
 
521
     qof_instance_mark_clean(&pr->db->inst);
 
522
 
 
523
   LEAVE ("commited");
 
524
   return;
 
525
}
 
526
 
 
527
/* ======================== END OF FILE ======================== */