PageRenderTime 42ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/src/engine/Query.c

http://github.com/mchochlov/Gnucash
C | 711 lines | 519 code | 116 blank | 76 comment | 80 complexity | d7c43f7e0c1fda799adedae56b4fdeac MD5 | raw file
Possible License(s): GPL-2.0
  1. /********************************************************************\
  2. * Query.c : api for finding transactions *
  3. * Copyright (C) 2000 Bill Gribble <grib@billgribble.com> *
  4. * Copyright (C) 2002 Linas Vepstas <linas@linas.org> *
  5. * *
  6. * This program is free software; you can redistribute it and/or *
  7. * modify it under the terms of the GNU General Public License as *
  8. * published by the Free Software Foundation; either version 2 of *
  9. * the License, or (at your option) any later version. *
  10. * *
  11. * This program is distributed in the hope that it will be useful, *
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  14. * GNU General Public License for more details. *
  15. * *
  16. * You should have received a copy of the GNU General Public License*
  17. * along with this program; if not, contact: *
  18. * *
  19. * Free Software Foundation Voice: +1-617-542-5942 *
  20. * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
  21. * Boston, MA 02110-1301, USA gnu@gnu.org *
  22. \********************************************************************/
  23. #include "config.h"
  24. #include <ctype.h>
  25. #include <glib.h>
  26. #include <math.h>
  27. #include <string.h>
  28. #include <sys/types.h>
  29. #include <regex.h>
  30. #ifdef HAVE_SYS_TIME_H
  31. # include <sys/time.h>
  32. #endif
  33. #ifdef HAVE_UNISTD_H
  34. # include <unistd.h>
  35. #endif
  36. #include "gnc-lot.h"
  37. #include "Account.h"
  38. #include "Query.h"
  39. #include "Transaction.h"
  40. #include "TransactionP.h"
  41. static QofLogModule log_module = GNC_MOD_QUERY;
  42. static GSList *
  43. build_param_list_internal (const char *first, va_list rest)
  44. {
  45. GSList *list = NULL;
  46. char const *param;
  47. for (param = first; param; param = va_arg (rest, const char *))
  48. list = g_slist_prepend (list, (gpointer)param);
  49. return (g_slist_reverse (list));
  50. }
  51. /********************************************************************
  52. * xaccQueryGetSplitsUniqueTrans
  53. * Get splits but no more than one from a given transaction.
  54. ********************************************************************/
  55. SplitList *
  56. xaccQueryGetSplitsUniqueTrans(QofQuery *q)
  57. {
  58. GList * splits = qof_query_run(q);
  59. GList * current;
  60. GList * result = NULL;
  61. GHashTable * trans_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
  62. for (current = splits; current; current = current->next)
  63. {
  64. Split *split = current->data;
  65. Transaction *trans = xaccSplitGetParent (split);
  66. if (!g_hash_table_lookup (trans_hash, trans))
  67. {
  68. g_hash_table_insert (trans_hash, trans, trans);
  69. result = g_list_prepend (result, split);
  70. }
  71. }
  72. g_hash_table_destroy (trans_hash);
  73. return g_list_reverse (result);
  74. }
  75. /********************************************************************
  76. * xaccQueryGetTransactions
  77. * Get transactions matching the query terms, specifying whether
  78. * we require some or all splits to match
  79. ********************************************************************/
  80. static void
  81. query_match_all_filter_func(gpointer key, gpointer value, gpointer user_data)
  82. {
  83. Transaction * t = key;
  84. int num_matches = GPOINTER_TO_INT(value);
  85. GList ** matches = user_data;
  86. if (num_matches == xaccTransCountSplits(t))
  87. {
  88. *matches = g_list_prepend(*matches, t);
  89. }
  90. }
  91. static void
  92. query_match_any_filter_func(gpointer key, gpointer value, gpointer user_data)
  93. {
  94. Transaction * t = key;
  95. GList ** matches = user_data;
  96. *matches = g_list_prepend(*matches, t);
  97. }
  98. TransList *
  99. xaccQueryGetTransactions (QofQuery * q, query_txn_match_t runtype)
  100. {
  101. GList * splits = qof_query_run(q);
  102. GList * current = NULL;
  103. GList * retval = NULL;
  104. GHashTable * trans_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
  105. Transaction * trans = NULL;
  106. gpointer val = NULL;
  107. int count = 0;
  108. /* iterate over matching splits, incrementing a match-count in
  109. * the hash table */
  110. for (current = splits; current; current = current->next)
  111. {
  112. trans = xaccSplitGetParent((Split *)(current->data));
  113. /* don't waste time looking up unless we need the count
  114. * information */
  115. if (runtype == QUERY_TXN_MATCH_ALL)
  116. {
  117. val = g_hash_table_lookup(trans_hash, trans);
  118. count = GPOINTER_TO_INT(val);
  119. }
  120. g_hash_table_insert(trans_hash, trans, GINT_TO_POINTER(count + 1));
  121. }
  122. /* now pick out the transactions that match */
  123. if (runtype == QUERY_TXN_MATCH_ALL)
  124. {
  125. g_hash_table_foreach(trans_hash, query_match_all_filter_func,
  126. &retval);
  127. }
  128. else
  129. {
  130. g_hash_table_foreach(trans_hash, query_match_any_filter_func,
  131. &retval);
  132. }
  133. g_hash_table_destroy(trans_hash);
  134. return retval;
  135. }
  136. /********************************************************************
  137. * xaccQueryGetLots
  138. * Get lots matching the query terms, specifying whether
  139. * we require some or all splits to match
  140. ********************************************************************/
  141. static void
  142. query_match_all_lot_filter_func(gpointer key, gpointer value, gpointer user_data)
  143. {
  144. GNCLot * l = key;
  145. int num_matches = GPOINTER_TO_INT(value);
  146. GList ** matches = user_data;
  147. if (num_matches == gnc_lot_count_splits(l))
  148. {
  149. *matches = g_list_prepend(*matches, l);
  150. }
  151. }
  152. static void
  153. query_match_any_lot_filter_func(gpointer key, gpointer value, gpointer user_data)
  154. {
  155. GNCLot * t = key;
  156. GList ** matches = user_data;
  157. *matches = g_list_prepend(*matches, t);
  158. }
  159. LotList *
  160. xaccQueryGetLots (QofQuery * q, query_txn_match_t runtype)
  161. {
  162. GList * splits = qof_query_run(q);
  163. GList * current = NULL;
  164. GList * retval = NULL;
  165. GHashTable * lot_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
  166. GNCLot * lot = NULL;
  167. gpointer val = NULL;
  168. int count = 0;
  169. /* iterate over matching splits, incrementing a match-count in
  170. * the hash table */
  171. for (current = splits; current; current = current->next)
  172. {
  173. lot = xaccSplitGetLot((Split *)(current->data));
  174. /* don't waste time looking up unless we need the count
  175. * information */
  176. if (runtype == QUERY_TXN_MATCH_ALL)
  177. {
  178. val = g_hash_table_lookup(lot_hash, lot);
  179. count = GPOINTER_TO_INT(val);
  180. }
  181. g_hash_table_insert(lot_hash, lot, GINT_TO_POINTER(count + 1));
  182. }
  183. /* now pick out the transactions that match */
  184. if (runtype == QUERY_TXN_MATCH_ALL)
  185. {
  186. g_hash_table_foreach(lot_hash, query_match_all_lot_filter_func,
  187. &retval);
  188. }
  189. else
  190. {
  191. g_hash_table_foreach(lot_hash, query_match_any_lot_filter_func,
  192. &retval);
  193. }
  194. g_hash_table_destroy(lot_hash);
  195. return retval;
  196. }
  197. /*******************************************************************
  198. * match-adding API
  199. *******************************************************************/
  200. void
  201. xaccQueryAddAccountMatch(QofQuery *q, AccountList *acct_list,
  202. QofGuidMatch how, QofQueryOp op)
  203. {
  204. GList *list = NULL;
  205. if (!q) return;
  206. for (; acct_list; acct_list = acct_list->next)
  207. {
  208. Account *acc = acct_list->data;
  209. const GncGUID *guid;
  210. if (!acc)
  211. {
  212. PWARN ("acct_list has NULL account");
  213. continue;
  214. }
  215. guid = qof_entity_get_guid (QOF_INSTANCE(acc));
  216. if (!guid)
  217. {
  218. PWARN ("acct returns NULL GncGUID");
  219. continue;
  220. }
  221. list = g_list_prepend (list, (gpointer)guid);
  222. }
  223. xaccQueryAddAccountGUIDMatch (q, list, how, op);
  224. g_list_free (list);
  225. }
  226. void
  227. xaccQueryAddAccountGUIDMatch(QofQuery *q, AccountGUIDList *guid_list,
  228. QofGuidMatch how, QofQueryOp op)
  229. {
  230. QofQueryPredData *pred_data;
  231. GSList *param_list = NULL;
  232. if (!q) return;
  233. if (!guid_list && how != QOF_GUID_MATCH_NULL)
  234. {
  235. g_warning("Got a NULL guid_list but the QofGuidMatch is not MATCH_NULL (but instead %d). In other words, the list of GUID matches is empty but it must contain something non-empty.", how);
  236. /* qof_query_guid_predicate() would trigger a g_warning as well */
  237. return;
  238. }
  239. pred_data = qof_query_guid_predicate (how, guid_list);
  240. if (!pred_data)
  241. return;
  242. switch (how)
  243. {
  244. case QOF_GUID_MATCH_ANY:
  245. case QOF_GUID_MATCH_NONE:
  246. param_list = qof_query_build_param_list (SPLIT_ACCOUNT, QOF_PARAM_GUID, NULL);
  247. break;
  248. case QOF_GUID_MATCH_ALL:
  249. param_list = qof_query_build_param_list (SPLIT_TRANS, TRANS_SPLITLIST,
  250. SPLIT_ACCOUNT_GUID, NULL);
  251. break;
  252. default:
  253. PERR ("Invalid match type: %d", how);
  254. }
  255. qof_query_add_term (q, param_list, pred_data, op);
  256. }
  257. void
  258. xaccQueryAddSingleAccountMatch(QofQuery *q, Account *acc, QofQueryOp op)
  259. {
  260. GList *list;
  261. const GncGUID *guid;
  262. if (!q || !acc)
  263. return;
  264. guid = qof_entity_get_guid (QOF_INSTANCE(acc));
  265. g_return_if_fail (guid);
  266. list = g_list_prepend (NULL, (gpointer)guid);
  267. xaccQueryAddAccountGUIDMatch (q, list, QOF_GUID_MATCH_ANY, op);
  268. g_list_free (list);
  269. }
  270. void
  271. xaccQueryAddStringMatch (QofQuery* q, const char *matchstring,
  272. gboolean case_sens, gboolean use_regexp,
  273. QofQueryOp op,
  274. const char * path, ...)
  275. {
  276. QofQueryPredData *pred_data;
  277. GSList *param_list;
  278. va_list ap;
  279. if (!path || !q)
  280. return;
  281. pred_data = qof_query_string_predicate (QOF_COMPARE_EQUAL, (char *)matchstring,
  282. (case_sens ? QOF_STRING_MATCH_NORMAL :
  283. QOF_STRING_MATCH_CASEINSENSITIVE),
  284. use_regexp);
  285. if (!pred_data)
  286. return;
  287. va_start (ap, path);
  288. param_list = build_param_list_internal (path, ap);
  289. va_end (ap);
  290. qof_query_add_term (q, param_list, pred_data, op);
  291. }
  292. void
  293. xaccQueryAddNumericMatch (QofQuery *q, gnc_numeric amount, QofNumericMatch sign,
  294. QofQueryCompare how, QofQueryOp op,
  295. const char * path, ...)
  296. {
  297. QofQueryPredData *pred_data;
  298. GSList *param_list;
  299. va_list ap;
  300. if (!q || !path)
  301. return;
  302. pred_data = qof_query_numeric_predicate (how, sign, amount);
  303. if (!pred_data)
  304. return;
  305. va_start (ap, path);
  306. param_list = build_param_list_internal (path, ap);
  307. va_end (ap);
  308. qof_query_add_term (q, param_list, pred_data, op);
  309. }
  310. /* The DateMatch queries match transactions whose posted date
  311. * is in a date range. If use_start is TRUE, then a matching
  312. * posted date will be greater than the start date. If
  313. * use_end is TRUE, then a match occurs for posted dates earlier
  314. * than the end date. If both flags are set, then *both*
  315. * conditions must hold ('and'). If neither flag is set, then
  316. * all transactions are matched.
  317. */
  318. void
  319. xaccQueryAddDateMatchTS (QofQuery * q,
  320. gboolean use_start, Timespec sts,
  321. gboolean use_end, Timespec ets,
  322. QofQueryOp op)
  323. {
  324. QofQuery *tmp_q = NULL;
  325. QofQueryPredData *pred_data;
  326. GSList *param_list;
  327. if (!q || (!use_start && !use_end))
  328. return;
  329. tmp_q = qof_query_create ();
  330. if (use_start)
  331. {
  332. pred_data = qof_query_date_predicate (QOF_COMPARE_GTE, QOF_DATE_MATCH_NORMAL, sts);
  333. if (!pred_data)
  334. {
  335. qof_query_destroy (tmp_q);
  336. return;
  337. }
  338. param_list = qof_query_build_param_list (SPLIT_TRANS, TRANS_DATE_POSTED, NULL);
  339. qof_query_add_term (tmp_q, param_list, pred_data, QOF_QUERY_AND);
  340. }
  341. if (use_end)
  342. {
  343. pred_data = qof_query_date_predicate (QOF_COMPARE_LTE, QOF_DATE_MATCH_NORMAL, ets);
  344. if (!pred_data)
  345. {
  346. qof_query_destroy (tmp_q);
  347. return;
  348. }
  349. param_list = qof_query_build_param_list (SPLIT_TRANS, TRANS_DATE_POSTED, NULL);
  350. qof_query_add_term (tmp_q, param_list, pred_data, QOF_QUERY_AND);
  351. }
  352. qof_query_merge_in_place (q, tmp_q, op);
  353. qof_query_destroy (tmp_q);
  354. }
  355. void
  356. xaccQueryGetDateMatchTS (QofQuery * q,
  357. Timespec * sts,
  358. Timespec * ets)
  359. {
  360. QofQueryPredData *term_data;
  361. GSList *param_list;
  362. GSList *terms, *tmp;
  363. sts->tv_sec = sts->tv_nsec = 0;
  364. ets->tv_sec = ets->tv_nsec = 0;
  365. param_list = qof_query_build_param_list (SPLIT_TRANS, TRANS_DATE_POSTED, NULL);
  366. terms = qof_query_get_term_type (q, param_list);
  367. g_slist_free(param_list);
  368. for (tmp = terms; tmp; tmp = g_slist_next(tmp))
  369. {
  370. term_data = tmp->data;
  371. if (term_data->how == QOF_COMPARE_GTE)
  372. qof_query_date_predicate_get_date(term_data, sts);
  373. if (term_data->how == QOF_COMPARE_LTE)
  374. qof_query_date_predicate_get_date(term_data, ets);
  375. }
  376. g_slist_free(terms);
  377. }
  378. /********************************************************************
  379. * xaccQueryAddDateMatch
  380. * Add a date filter to an existing query.
  381. ********************************************************************/
  382. void
  383. xaccQueryAddDateMatch(QofQuery * q,
  384. gboolean use_start, int sday, int smonth, int syear,
  385. gboolean use_end, int eday, int emonth, int eyear,
  386. QofQueryOp op)
  387. {
  388. /* gcc -O3 will auto-inline this function, avoiding a call overhead */
  389. xaccQueryAddDateMatchTS (q, use_start,
  390. gnc_dmy2timespec(sday, smonth, syear),
  391. use_end,
  392. gnc_dmy2timespec_end(eday, emonth, eyear),
  393. op);
  394. }
  395. /********************************************************************
  396. * xaccQueryAddDateMatchTT
  397. * Add a date filter to an existing query.
  398. ********************************************************************/
  399. void
  400. xaccQueryAddDateMatchTT(QofQuery * q,
  401. gboolean use_start,
  402. time_t stt,
  403. gboolean use_end,
  404. time_t ett,
  405. QofQueryOp op)
  406. {
  407. Timespec sts;
  408. Timespec ets;
  409. sts.tv_sec = (long long)stt;
  410. sts.tv_nsec = 0;
  411. ets.tv_sec = (long long)ett;
  412. ets.tv_nsec = 0;
  413. /* gcc -O3 will auto-inline this function, avoiding a call overhead */
  414. xaccQueryAddDateMatchTS (q, use_start, sts,
  415. use_end, ets, op);
  416. }
  417. void
  418. xaccQueryGetDateMatchTT (QofQuery * q,
  419. time_t * stt,
  420. time_t * ett)
  421. {
  422. Timespec sts;
  423. Timespec ets;
  424. xaccQueryGetDateMatchTS (q, &sts, &ets);
  425. *stt = sts.tv_sec;
  426. *ett = ets.tv_sec;
  427. }
  428. void
  429. xaccQueryAddClearedMatch(QofQuery * q, cleared_match_t how, QofQueryOp op)
  430. {
  431. QofQueryPredData *pred_data;
  432. GSList *param_list;
  433. char chars[6];
  434. int i = 0;
  435. if (!q)
  436. return;
  437. if (how & CLEARED_CLEARED)
  438. chars[i++] = CREC;
  439. if (how & CLEARED_RECONCILED)
  440. chars[i++] = YREC;
  441. if (how & CLEARED_FROZEN)
  442. chars[i++] = FREC;
  443. if (how & CLEARED_NO)
  444. chars[i++] = NREC;
  445. if (how & CLEARED_VOIDED)
  446. chars[i++] = VREC;
  447. chars[i] = '\0';
  448. pred_data = qof_query_char_predicate (QOF_CHAR_MATCH_ANY, chars);
  449. if (!pred_data)
  450. return;
  451. param_list = qof_query_build_param_list (SPLIT_RECONCILE, NULL);
  452. qof_query_add_term (q, param_list, pred_data, op);
  453. }
  454. void
  455. xaccQueryAddGUIDMatch(QofQuery * q, const GncGUID *guid,
  456. QofIdType id_type, QofQueryOp op)
  457. {
  458. GSList *param_list = NULL;
  459. if (!q || !guid || !id_type)
  460. return;
  461. if (!safe_strcmp (id_type, GNC_ID_SPLIT))
  462. param_list = qof_query_build_param_list (QOF_PARAM_GUID, NULL);
  463. else if (!safe_strcmp (id_type, GNC_ID_TRANS))
  464. param_list = qof_query_build_param_list (SPLIT_TRANS, QOF_PARAM_GUID, NULL);
  465. else if (!safe_strcmp (id_type, GNC_ID_ACCOUNT))
  466. param_list = qof_query_build_param_list (SPLIT_ACCOUNT, QOF_PARAM_GUID, NULL);
  467. else
  468. PERR ("Invalid match type: %s", id_type);
  469. qof_query_add_guid_match (q, param_list, guid, op);
  470. }
  471. void
  472. xaccQueryAddKVPMatch(QofQuery *q, GSList *path, const KvpValue *value,
  473. QofQueryCompare how, QofIdType id_type,
  474. QofQueryOp op)
  475. {
  476. GSList *param_list = NULL;
  477. QofQueryPredData *pred_data;
  478. if (!q || !path || !value || !id_type)
  479. return;
  480. pred_data = qof_query_kvp_predicate (how, path, value);
  481. if (!pred_data)
  482. return;
  483. if (!safe_strcmp (id_type, GNC_ID_SPLIT))
  484. param_list = qof_query_build_param_list (SPLIT_KVP, NULL);
  485. else if (!safe_strcmp (id_type, GNC_ID_TRANS))
  486. param_list = qof_query_build_param_list (SPLIT_TRANS, TRANS_KVP, NULL);
  487. else if (!safe_strcmp (id_type, GNC_ID_ACCOUNT))
  488. param_list = qof_query_build_param_list (SPLIT_ACCOUNT, ACCOUNT_KVP, NULL);
  489. else
  490. PERR ("Invalid match type: %s", id_type);
  491. qof_query_add_term (q, param_list, pred_data, op);
  492. }
  493. /*******************************************************************
  494. * xaccQueryGetEarliestDateFound
  495. *******************************************************************/
  496. time_t
  497. xaccQueryGetEarliestDateFound(QofQuery * q)
  498. {
  499. GList * spl;
  500. Split * sp;
  501. time_t earliest;
  502. if (!q) return 0;
  503. spl = qof_query_last_run (q);
  504. if (!spl) return 0;
  505. /* Safe until 2038 on archs where time_t is 32bit */
  506. sp = spl->data;
  507. earliest = (time_t) sp->parent->date_posted.tv_sec;
  508. for (; spl; spl = spl->next)
  509. {
  510. sp = spl->data;
  511. if (sp->parent->date_posted.tv_sec < earliest)
  512. {
  513. earliest = (time_t) sp->parent->date_posted.tv_sec;
  514. }
  515. }
  516. return earliest;
  517. }
  518. /*******************************************************************
  519. * xaccQueryGetLatestDateFound
  520. *******************************************************************/
  521. time_t
  522. xaccQueryGetLatestDateFound(QofQuery * q)
  523. {
  524. Split * sp;
  525. GList * spl;
  526. time_t latest = 0;
  527. if (!q) return 0;
  528. spl = qof_query_last_run (q);
  529. if (!spl) return 0;
  530. for (; spl; spl = spl->next)
  531. {
  532. sp = spl->data;
  533. if (sp->parent->date_posted.tv_sec > latest)
  534. {
  535. latest = (time_t) sp->parent->date_posted.tv_sec;
  536. }
  537. }
  538. return latest;
  539. }
  540. void
  541. xaccQueryAddDescriptionMatch(QofQuery *q, const char *m, gboolean c, gboolean r,
  542. QofQueryOp o)
  543. {
  544. xaccQueryAddStringMatch ((q), (m), (c), (r), (o), SPLIT_TRANS,
  545. TRANS_DESCRIPTION, NULL);
  546. }
  547. void
  548. xaccQueryAddNumberMatch(QofQuery *q, const char *m, gboolean c, gboolean r,
  549. QofQueryOp o)
  550. {
  551. xaccQueryAddStringMatch ((q), (m), (c), (r), (o), SPLIT_TRANS,
  552. TRANS_NUM, NULL);
  553. }
  554. void
  555. xaccQueryAddActionMatch(QofQuery *q, const char *m, gboolean c, gboolean r,
  556. QofQueryOp o)
  557. {
  558. xaccQueryAddStringMatch ((q), (m), (c), (r), (o), SPLIT_ACTION, NULL);
  559. }
  560. void
  561. xaccQueryAddMemoMatch(QofQuery *q, const char *m, gboolean c, gboolean r,
  562. QofQueryOp o)
  563. {
  564. xaccQueryAddStringMatch ((q), (m), (c), (r), (o), SPLIT_MEMO, NULL);
  565. }
  566. void
  567. xaccQueryAddValueMatch(QofQuery *q, gnc_numeric amt, QofNumericMatch sgn,
  568. QofQueryCompare how, QofQueryOp op)
  569. {
  570. xaccQueryAddNumericMatch ((q), (amt), (sgn), (how), (op),
  571. SPLIT_VALUE, NULL);
  572. }
  573. void
  574. xaccQueryAddSharePriceMatch(QofQuery *q, gnc_numeric amt, QofQueryCompare how,
  575. QofQueryOp op)
  576. {
  577. xaccQueryAddNumericMatch ((q), (amt), QOF_NUMERIC_MATCH_ANY, (how), (op),
  578. SPLIT_SHARE_PRICE, NULL);
  579. }
  580. void
  581. xaccQueryAddSharesMatch(QofQuery *q, gnc_numeric amt, QofQueryCompare how,
  582. QofQueryOp op)
  583. {
  584. xaccQueryAddNumericMatch ((q), (amt), QOF_NUMERIC_MATCH_ANY, (how), (op),
  585. SPLIT_AMOUNT, NULL);
  586. }
  587. void
  588. xaccQueryAddBalanceMatch(QofQuery *q, QofQueryCompare bal, QofQueryOp op)
  589. {
  590. xaccQueryAddNumericMatch(
  591. (q), gnc_numeric_zero(), QOF_NUMERIC_MATCH_ANY,
  592. ((bal) ? QOF_COMPARE_EQUAL : QOF_COMPARE_NEQ), (op),
  593. SPLIT_TRANS, TRANS_IMBALANCE, NULL);
  594. }
  595. /* ======================== END OF FILE ======================= */