/[pgestraier]/trunk/pgest.c
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /trunk/pgest.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 56 - (show annotations)
Thu May 11 15:52:50 2006 UTC (17 years, 11 months ago) by dpavlin
File MIME type: text/plain
File size: 18049 byte(s)
fix multiple attributes delimited with {{!}}, fix warnings
1 /*
2 * integrate Hyper Estraier into PostgreSQL
3 *
4 * Dobrica Pavlinusic <dpavlin@rot13.org> 2005-05-19
5 *
6 * TODO:
7 * - all
8 *
9 * NOTES:
10 * - clear structures with memset to support hash indexes (who whould like
11 * to create hash index on table returned from function?)
12 * - number of returned rows is set by PostgreSQL evaluator, see:
13 * http://archives.postgresql.org/pgsql-hackers/2005-02/msg00546.php
14 *
15 * Based on:
16 * - C example from PostgreSQL documentation (BSD licence)
17 * - coreexample002.c and nodeexample002.c from Hyper Estraier (GPL)
18 * - _textin/_textout from pgcurl.c (LGPL)
19 *
20 * This code is licenced under GPL
21 */
22
23 #include "postgres.h"
24 #include "fmgr.h"
25 #include "funcapi.h"
26 #include "utils/builtins.h"
27 #include "utils/array.h"
28 #include "utils/lsyscache.h"
29 #include "miscadmin.h"
30 #include <estraier.h>
31 #include <cabin.h>
32 #include <estnode.h>
33
34 #define _textin(str) DirectFunctionCall1(textin, CStringGetDatum(str))
35 #define _textout(str) DatumGetPointer(DirectFunctionCall1(textout, PointerGetDatum(str)))
36 #define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp)))
37 #define GET_TEXT(cstrp) DatumGetTextP(DirectFunctionCall1(textin, CStringGetDatum(cstrp)))
38
39 /* SortMem got renamed in PostgreSQL 8.0 */
40 #ifndef SortMem
41 #define SortMem 16 * 1024
42 #endif
43
44 #define ATTR_DELIMITER "{{!}}"
45
46 /* prototype */
47 char *attr2text(ESTDOC *doc, char *attr);
48 char *node_attr2text(ESTRESDOC *rdoc, char *attr);
49 void cond_add_attr(ESTCOND *cond, char *attr);
50
51
52 /* work in progress */
53 PG_FUNCTION_INFO_V1(pgest_attr);
54 Datum pgest_attr(PG_FUNCTION_ARGS)
55 {
56 ArrayType *attr_arr = PG_GETARG_ARRAYTYPE_P(6);
57 Oid attr_element_type = ARR_ELEMTYPE(attr_arr);
58 int attr_ndims = ARR_NDIM(attr_arr);
59 int *attr_dim_counts = ARR_DIMS(attr_arr);
60 int *attr_dim_lower_bounds = ARR_LBOUND(attr_arr);
61 int ncols = 0;
62 int nrows = 0;
63 int indx[MAXDIM];
64 int16 attr_len;
65 bool attr_byval;
66 char attr_align;
67 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
68 AttInMetadata *attinmeta;
69 TupleDesc tupdesc;
70 Tuplestorestate *tupstore = NULL;
71 HeapTuple tuple;
72 MemoryContext per_query_ctx;
73 MemoryContext oldcontext;
74 Datum dvalue;
75 char **values;
76 int rsinfo_ncols;
77 int i, j;
78 /* estvars */
79 ESTDB *db;
80 ESTCOND *cond;
81 ESTDOC *doc;
82 int ecode, *est_result, resnum;
83 int limit = 0;
84 int offset = 0;
85
86 char *index_path;
87 char *query;
88 char *attr;
89 char *order;
90
91
92 /* only allow 1D input array */
93 if (attr_ndims == 1)
94 {
95 ncols = attr_dim_counts[0];
96 }
97 else
98 ereport(ERROR,
99 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
100 errmsg("invalid input array"),
101 errdetail("Input array must have 1 dimension")));
102
103 /* check to see if caller supports us returning a tuplestore */
104 if (!rsinfo || !(rsinfo->allowedModes & SFRM_Materialize))
105 ereport(ERROR,
106 (errcode(ERRCODE_SYNTAX_ERROR),
107 errmsg("materialize mode required, but it is not " \
108 "allowed in this context")));
109
110 /* get info about element type needed to construct the array */
111 get_typlenbyvalalign(attr_element_type, &attr_len, &attr_byval, &attr_align);
112
113 /* get the requested return tuple description */
114 tupdesc = rsinfo->expectedDesc;
115 rsinfo_ncols = tupdesc->natts;
116
117 /*
118 * The requested tuple description better match up with the array
119 * we were given.
120 */
121 if (rsinfo_ncols != ncols)
122 ereport(ERROR,
123 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
124 errmsg("invalid input array"),
125 errdetail("Number of elements in array must match number of query specified columns.")));
126
127 /* OK, use it */
128 attinmeta = TupleDescGetAttInMetadata(tupdesc);
129
130 /* Now go to work */
131 rsinfo->returnMode = SFRM_Materialize;
132
133 per_query_ctx = fcinfo->flinfo->fn_mcxt;
134 oldcontext = MemoryContextSwitchTo(per_query_ctx);
135
136 /* initialize our tuplestore */
137 tupstore = tuplestore_begin_heap(true, false, SortMem);
138
139
140 /* take rest of arguments from function */
141
142 /* index path */
143 if (PG_ARGISNULL(0)) {
144 ereport(ERROR,
145 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
146 errmsg("index path can't be null"),
147 errdetail("Index path must be valid full path to HyperEstraier index")));
148 }
149 index_path = _textout(PG_GETARG_TEXT_P(0));
150
151 /* query string */
152 if (PG_ARGISNULL(1)) {
153 query = "";
154 } else {
155 query = _textout(PG_GETARG_TEXT_P(1));
156 }
157
158 /* atribute filter */
159 if (PG_ARGISNULL(2)) {
160 attr = "";
161 } else {
162 attr = _textout(PG_GETARG_TEXT_P(2));
163 }
164
165 /* sort order */
166 if (PG_ARGISNULL(3)) {
167 order = "";
168 } else {
169 order = _textout(PG_GETARG_TEXT_P(3));
170 }
171
172
173 /* limit */
174 if (PG_ARGISNULL(4)) {
175 limit = 0;
176 } else {
177 limit = PG_GETARG_INT32(4);
178 }
179
180 /* offset */
181 if (PG_ARGISNULL(5)) {
182 offset = 0;
183 } else {
184 offset = PG_GETARG_INT32(5);
185 }
186
187
188 /* open the database */
189 elog(DEBUG1, "pgest_attr: est_db_open(%s)", index_path);
190
191 if(!(db = est_db_open(index_path, ESTDBREADER, &ecode))){
192 ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
193 errmsg("est_db_open: can't open %s: %d", index_path, ecode),
194 errdetail(est_err_msg(ecode))));
195 }
196
197 elog(DEBUG1, "pgest_attr: query[%s] attr[%s] limit %d offset %d", query, (PG_ARGISNULL(2) ? "NULL" : attr), limit, offset);
198
199 /* create a search condition object */
200 if (!(cond = est_cond_new())) {
201 ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED),
202 errmsg("pgest_attr: est_cond_new failed")));
203 }
204
205 /* set the search phrase to the search condition object */
206 if (! PG_ARGISNULL(1) && strlen(query) > 0)
207 est_cond_set_phrase(cond, query);
208
209 /* minimum valid attribute length is 10: @a STREQ a */
210 if (! PG_ARGISNULL(2) && strlen(attr) >= 10) {
211 elog(DEBUG1,"attributes: %s", attr);
212 cond_add_attr(cond, attr);
213 }
214
215 /* set the search phrase to the search condition object */
216 if (! PG_ARGISNULL(3) && strlen(order) > 0) {
217 elog(DEBUG1,"est_cond_set_order(%s)", order);
218 est_cond_set_order(cond, order);
219 }
220
221 if (limit) {
222 elog(DEBUG1,"est_cond_set_max(%d)", limit + offset);
223 est_cond_set_max(cond, limit + offset);
224 }
225
226 /* get the result of search */
227 est_result = est_db_search(db, cond, &resnum, NULL);
228
229 /* check if results exists */
230 if ( 0 == resnum ) {
231 elog(INFO, "pgest_attr: no results for: %s", query );
232 }
233
234 /* total number of tuples to be returned */
235 if (limit && limit < resnum) {
236 nrows = limit;
237 } else {
238 nrows = resnum - offset;
239 }
240
241
242 elog(DEBUG1, "pgest_attr: found %d hits for %s", resnum, query);
243
244 values = (char **) palloc(ncols * sizeof(char *));
245
246 for (i = 0; i < nrows; i++)
247 {
248
249 /* get result from estraier */
250 if (! ( doc = est_db_get_doc(db, est_result[i + offset], 0)) ) {
251 elog(INFO, "pgest_attr: can't find result %d", i + offset);
252 } else {
253 elog(DEBUG1, "URI: %s\n Title: %s\n",
254 est_doc_attr(doc, "@uri"),
255 est_doc_attr(doc, "@title")
256 );
257 }
258
259 /* iterate over results */
260 for (j = 0; j < ncols; j++)
261 {
262 bool isnull;
263
264 /* array value of this position */
265 indx[0] = j + attr_dim_lower_bounds[0];
266
267 dvalue = array_ref(attr_arr, attr_ndims, indx, -1, attr_len, attr_byval, attr_align, &isnull);
268
269 if (!isnull && doc)
270 values[j] = DatumGetCString(
271 attr2text(doc,
272 (char *)DirectFunctionCall1(textout, dvalue)
273 ));
274 else
275 values[j] = NULL;
276 }
277 /* construct the tuple */
278 tuple = BuildTupleFromCStrings(attinmeta, values);
279
280 /* now store it */
281 tuplestore_puttuple(tupstore, tuple);
282
283 /* delete estraier document object */
284 if (doc) est_doc_delete(doc);
285 }
286
287 tuplestore_donestoring(tupstore);
288 rsinfo->setResult = tupstore;
289
290 /*
291 * SFRM_Materialize mode expects us to return a NULL Datum. The actual
292 * tuples are in our tuplestore and passed back through
293 * rsinfo->setResult. rsinfo->setDesc is set to the tuple description
294 * that we actually used to build our tuples with, so the caller can
295 * verify we did what it was expecting.
296 */
297 rsinfo->setDesc = tupdesc;
298 MemoryContextSwitchTo(oldcontext);
299
300 est_cond_delete(cond);
301
302 if(!est_db_close(db, &ecode)){
303 ereport(ERROR, (errcode(ERRCODE_IO_ERROR),
304 errmsg("est_db_close: %d", ecode),
305 errdetail(est_err_msg(ecode))));
306 }
307
308 return (Datum) 0;
309 }
310
311
312 /* make text var from attr */
313 char *attr2text(ESTDOC *doc, char *attr) {
314 char *val;
315 const char *attrval;
316 int len;
317 int attrlen;
318
319 if (! doc) return (Datum) NULL;
320
321 elog(DEBUG1, "doc: %p, attr: %s", doc, attr);
322
323 if ( (attrval = est_doc_attr(doc, attr)) && (attrlen = strlen(attrval)) ) {
324 val = (char *) palloc(attrlen * sizeof(char));
325 } else {
326 return (Datum) NULL;
327 }
328
329 len = strlen(attrval);
330 elog(DEBUG1, "attr2text(%s) = '%s' %d bytes", attr, attrval, len);
331
332 len++;
333 len *= sizeof(char);
334
335 elog(DEBUG2, "palloc(%d)", len);
336
337 val = palloc(len);
338
339 memset(val, 0, len);
340 strncpy(val, attrval, len);
341
342 elog(DEBUG2, "val=%s", val);
343
344 return val;
345 }
346
347 /*
348 * variation on theme: use node API which doesn't open index on
349 * every query which is much faster for large indexes
350 *
351 */
352
353 /* select * from pgest( */
354 #define _arg_node_uri 0
355 #define _arg_login 1
356 #define _arg_passwd 2
357 #define _arg_depth 3
358 #define _arg_query 4
359 #define _arg_attr 5
360 #define _arg_order 6
361 #define _arg_limit 7
362 #define _arg_offset 8
363 #define _arg_attr_array 9
364 /* as (foo text, ... ); */
365
366
367 PG_FUNCTION_INFO_V1(pgest_node);
368 Datum pgest_node(PG_FUNCTION_ARGS)
369 {
370 ArrayType *attr_arr = PG_GETARG_ARRAYTYPE_P(_arg_attr_array);
371 Oid attr_element_type = ARR_ELEMTYPE(attr_arr);
372 int attr_ndims = ARR_NDIM(attr_arr);
373 int *attr_dim_counts = ARR_DIMS(attr_arr);
374 int *attr_dim_lower_bounds = ARR_LBOUND(attr_arr);
375 int ncols = 0;
376 int nrows = 0;
377 int indx[MAXDIM];
378 int16 attr_len;
379 bool attr_byval;
380 char attr_align;
381 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
382 AttInMetadata *attinmeta;
383 TupleDesc tupdesc;
384 Tuplestorestate *tupstore = NULL;
385 HeapTuple tuple;
386 MemoryContext per_query_ctx;
387 MemoryContext oldcontext;
388 Datum dvalue;
389 char **values;
390 int rsinfo_ncols;
391 int i, j;
392 /* estvars */
393 ESTNODE *node;
394 ESTCOND *cond;
395 ESTNODERES *nres;
396 ESTRESDOC *rdoc;
397 int resnum = 0;
398 int limit = 0;
399 int offset = 0;
400 int depth = 0;
401
402 char *node_url;
403 char *user, *passwd;
404 char *query;
405 char *attr;
406 char *order;
407
408
409 /* only allow 1D input array */
410 if (attr_ndims == 1)
411 {
412 ncols = attr_dim_counts[0];
413 }
414 else
415 ereport(ERROR,
416 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
417 errmsg("invalid input array"),
418 errdetail("Input array must have 1 dimension")));
419
420 /* check to see if caller supports us returning a tuplestore */
421 if (!rsinfo || !(rsinfo->allowedModes & SFRM_Materialize))
422 ereport(ERROR,
423 (errcode(ERRCODE_SYNTAX_ERROR),
424 errmsg("materialize mode required, but it is not " \
425 "allowed in this context")));
426
427 /* get info about element type needed to construct the array */
428 get_typlenbyvalalign(attr_element_type, &attr_len, &attr_byval, &attr_align);
429
430 /* get the requested return tuple description */
431 tupdesc = rsinfo->expectedDesc;
432 rsinfo_ncols = tupdesc->natts;
433
434 /*
435 * The requested tuple description better match up with the array
436 * we were given.
437 */
438 if (rsinfo_ncols != ncols)
439 ereport(ERROR,
440 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
441 errmsg("invalid input array"),
442 errdetail("Number of elements in array must match number of query specified columns.")));
443
444 /* OK, use it */
445 attinmeta = TupleDescGetAttInMetadata(tupdesc);
446
447 /* Now go to work */
448 rsinfo->returnMode = SFRM_Materialize;
449
450 per_query_ctx = fcinfo->flinfo->fn_mcxt;
451 oldcontext = MemoryContextSwitchTo(per_query_ctx);
452
453 /* initialize our tuplestore */
454 tupstore = tuplestore_begin_heap(true, false, SortMem);
455
456
457 /* take rest of arguments from function */
458
459 /* node URL */
460 if (PG_ARGISNULL(_arg_node_uri)) {
461 ereport(ERROR,
462 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
463 errmsg("node URL can't be null"),
464 errdetail("Node URL must be valid URL to HyperEstraier node")));
465 }
466 node_url = _textout(PG_GETARG_TEXT_P(_arg_node_uri));
467
468 /* login and password */
469 if (PG_ARGISNULL(_arg_login) || PG_ARGISNULL(_arg_passwd)) {
470 ereport(ERROR,
471 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
472 errmsg("username and password can't be NULL"),
473 errdetail("You must specify valid username and password to HyperEstraier node")));
474 }
475 user = _textout(PG_GETARG_TEXT_P(_arg_login));
476 passwd = _textout(PG_GETARG_TEXT_P(_arg_passwd));
477
478 /* depth of search */
479 if (PG_ARGISNULL(_arg_depth)) {
480 depth = 0;
481 } else {
482 depth = PG_GETARG_INT32(_arg_depth);
483 }
484
485 /* query string */
486 if (PG_ARGISNULL(_arg_query)) {
487 query = "";
488 } else {
489 query = _textout(PG_GETARG_TEXT_P(_arg_query));
490 }
491
492 /* atribute filter */
493 if (PG_ARGISNULL(_arg_attr)) {
494 attr = "";
495 } else {
496 attr = _textout(PG_GETARG_TEXT_P(_arg_attr));
497 }
498
499 /* sort order */
500 if (PG_ARGISNULL(_arg_order)) {
501 order = "";
502 } else {
503 order = _textout(PG_GETARG_TEXT_P(_arg_order));
504 }
505
506
507 /* limit */
508 if (PG_ARGISNULL(_arg_limit)) {
509 limit = 0;
510 } else {
511 limit = PG_GETARG_INT32(_arg_limit);
512 }
513
514 /* offset */
515 if (PG_ARGISNULL(_arg_offset)) {
516 offset = 0;
517 } else {
518 offset = PG_GETARG_INT32(_arg_offset);
519 }
520
521 /* initialize the network environment */
522 if(!est_init_net_env()){
523 ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED),
524 errmsg("pgest_node: can't create network enviroment")));
525 }
526
527 /* create the node connection object */
528 elog(DEBUG1, "pgest_node: est_node_new(%s) as %s", node_url, user);
529 node = est_node_new(node_url);
530 est_node_set_auth(node, user, passwd);
531
532 elog(DEBUG1, "pgest_node: node: %s (d:%d) query[%s] attr[%s] limit %d offset %d", node_url, depth, query, (PG_ARGISNULL(_arg_attr) ? "NULL" : attr), limit, offset);
533
534 /* create a search condition object */
535 if (!(cond = est_cond_new())) {
536 ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED),
537 errmsg("pgest_node: est_cond_new failed")));
538 }
539
540 /* set the search phrase to the search condition object */
541 if (! PG_ARGISNULL(_arg_query) && strlen(query) > 0)
542 est_cond_set_phrase(cond, query);
543
544 /* minimum valid attribute length is 10: @a STREQ a */
545 if (! PG_ARGISNULL(_arg_attr) && strlen(attr) >= 10) {
546 elog(DEBUG1,"attributes: %s", attr);
547 cond_add_attr(cond, attr);
548 }
549
550 /* set the search phrase to the search condition object */
551 if (! PG_ARGISNULL(_arg_order) && strlen(order) > 0) {
552 elog(DEBUG1,"est_cond_set_order(%s)", order);
553 est_cond_set_order(cond, order);
554 }
555
556 if (limit) {
557 elog(DEBUG1,"est_cond_set_max(%d)", limit + offset);
558 est_cond_set_max(cond, limit + offset);
559 }
560
561 if (offset) {
562 elog(DEBUG1,"est_cond_set_skip(%d)", offset);
563 est_cond_set_skip(cond, offset);
564 }
565
566 /* get the result of search */
567 nres = est_node_search(node, cond, depth);
568
569 if (! nres) {
570 int status = est_node_status(node);
571 est_cond_delete(cond);
572 est_node_delete(node);
573 est_free_net_env();
574 ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED),
575 errmsg("pgest_node: search failed, node status %d", status)));
576 }
577
578 /* get number of results */
579 resnum = est_noderes_doc_num(nres);
580
581 /* check if results exists */
582 if ( 0 == resnum ) {
583 elog(INFO, "pgest_node: no results for: %s", query );
584 }
585
586 /* total number of tuples to be returned */
587 if (limit && limit < resnum) {
588 nrows = limit;
589 } else {
590 nrows = resnum - offset;
591 }
592
593 elog(DEBUG1, "pgest_node: found %d hits for %s", resnum, query);
594
595
596 values = (char **) palloc(ncols * sizeof(char *));
597
598 for (i = 0; i < nrows; i++)
599 {
600
601 /* get result from estraier */
602 if (! ( rdoc = est_noderes_get_doc(nres, i) )) {
603 elog(INFO, "pgest_node: can't find result %d", i + offset);
604 } else {
605 elog(DEBUG1, "URI: %s\n Title: %s\n",
606 est_resdoc_attr(rdoc, "@uri"),
607 est_resdoc_attr(rdoc, "@title")
608 );
609 }
610
611 /* iterate over results */
612 for (j = 0; j < ncols; j++)
613 {
614 bool isnull;
615
616 /* array value of this position */
617 indx[0] = j + attr_dim_lower_bounds[0];
618
619 dvalue = array_ref(attr_arr, attr_ndims, indx, -1, attr_len, attr_byval, attr_align, &isnull);
620
621 if (!isnull && rdoc)
622 values[j] = DatumGetCString(
623 node_attr2text(rdoc,
624 (char *)DirectFunctionCall1(textout, dvalue)
625 ));
626 else
627 values[j] = NULL;
628 }
629 /* construct the tuple */
630 tuple = BuildTupleFromCStrings(attinmeta, values);
631
632 /* now store it */
633 tuplestore_puttuple(tupstore, tuple);
634
635 }
636
637 tuplestore_donestoring(tupstore);
638 rsinfo->setResult = tupstore;
639
640 /*
641 * SFRM_Materialize mode expects us to return a NULL Datum. The actual
642 * tuples are in our tuplestore and passed back through
643 * rsinfo->setResult. rsinfo->setDesc is set to the tuple description
644 * that we actually used to build our tuples with, so the caller can
645 * verify we did what it was expecting.
646 */
647 rsinfo->setDesc = tupdesc;
648 MemoryContextSwitchTo(oldcontext);
649
650 /* delete the node result object */
651 est_noderes_delete(nres);
652
653 /* destroy the search condition object */
654 est_cond_delete(cond);
655
656 /* destroy the node object */
657 est_node_delete(node);
658
659 /* free the networking environment */
660 est_free_net_env();
661
662 return (Datum) 0;
663 }
664
665 /* make text var from node attr */
666 char *node_attr2text(ESTRESDOC *rdoc, char *attr) {
667 char *val;
668 const char *attrval;
669 int len;
670 int attrlen;
671
672 if (! rdoc) return (Datum) NULL;
673
674 elog(DEBUG1, "doc: %p, attr: %s", rdoc, attr);
675
676 if ( (attrval = est_resdoc_attr(rdoc, attr)) && (attrlen = strlen(attrval)) ) {
677 val = (char *) palloc(attrlen * sizeof(char));
678 } else {
679 return (Datum) NULL;
680 }
681
682 len = strlen(attrval);
683 elog(DEBUG1, "node_attr2text(%s) = '%s' %d bytes", attr, attrval, len);
684
685 len++;
686 len *= sizeof(char);
687
688 elog(DEBUG2, "palloc(%d)", len);
689
690 val = palloc(len);
691
692 memset(val, 0, len);
693 strncpy(val, attrval, len);
694
695 elog(DEBUG2, "val=%s", val);
696
697 return val;
698 }
699
700 /* parse attributes and add them to confition */
701 void cond_add_attr(ESTCOND *cond, char *attr) {
702 char *next;
703 char *curr_attr;
704 while ( strlen(attr) > 0 ) {
705 printf("len [%s] = %d\n", attr, strlen(attr));
706 if ((next = strstr(attr, ATTR_DELIMITER)) != NULL) {
707 curr_attr = palloc( next - attr + 1 );
708 memcpy(curr_attr, attr, next-attr);
709 curr_attr[next-attr] = '\0';
710 next += strlen(ATTR_DELIMITER);
711 } else {
712 next = "";
713 curr_attr = attr;
714 }
715 elog(DEBUG1, "est_cond_add_attr(%s)", curr_attr);
716 est_cond_add_attr(cond, curr_attr);
717 attr = next;
718 }
719 }

  ViewVC Help
Powered by ViewVC 1.1.26