--- trunk/pgest.c 2005/05/26 00:16:48 16 +++ trunk/pgest.c 2005/10/29 18:54:40 49 @@ -14,7 +14,7 @@ * * Based on: * - C example from PostgreSQL documentation (BSD licence) - * - example002.c from Hyper Estraier (GPL) + * - coreexample002.c and nodeexample002.c from Hyper Estraier (GPL) * - _textin/_textout from pgcurl.c (LGPL) * * This code is licenced under GPL @@ -28,259 +28,401 @@ #include "miscadmin.h" #include #include +#include #define _textin(str) DirectFunctionCall1(textin, CStringGetDatum(str)) #define _textout(str) DatumGetPointer(DirectFunctionCall1(textout, PointerGetDatum(str))) #define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp))) #define GET_TEXT(cstrp) DatumGetTextP(DirectFunctionCall1(textin, CStringGetDatum(cstrp))) +/* SortMem got renamed in PostgreSQL 8.0 */ +#ifndef SortMem + #define SortMem 16 * 1024 +#endif + +#define ATTR_DELIMITER "{{!}}" + /* prototype */ char *attr2text(ESTDOC *doc, char *attr); +char *node_attr2text(ESTRESDOC *rdoc, char *attr); -ESTDB *db; -ESTCOND *cond; -ESTDOC *doc; -const CBLIST *texts; -int ecode, *est_result, resnum, i, j; -int limit = 0; -int offset = 0; - -/* define PostgreSQL v1 function */ -PG_FUNCTION_INFO_V1(pgest); -Datum pgest(PG_FUNCTION_ARGS) { - - FuncCallContext *funcctx; - int call_cntr; - int max_calls; - TupleDesc tupdesc; - TupleTableSlot *slot; + +/* work in progress */ +PG_FUNCTION_INFO_V1(pgest_attr); +Datum pgest_attr(PG_FUNCTION_ARGS) +{ + ArrayType *attr_arr = PG_GETARG_ARRAYTYPE_P(6); + Oid attr_element_type = ARR_ELEMTYPE(attr_arr); + int attr_ndims = ARR_NDIM(attr_arr); + int *attr_dim_counts = ARR_DIMS(attr_arr); + int *attr_dim_lower_bounds = ARR_LBOUND(attr_arr); + int ncols = 0; + int nrows = 0; + int indx[MAXDIM]; + int16 attr_len; + bool attr_byval; + char attr_align; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; AttInMetadata *attinmeta; + TupleDesc tupdesc; + Tuplestorestate *tupstore = NULL; + HeapTuple tuple; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + Datum dvalue; + char **values; + int rsinfo_ncols; + int i, j; + /* estvars */ + ESTDB *db; + ESTCOND *cond; + ESTDOC *doc; + const CBLIST *texts; + int ecode, *est_result, resnum; + int limit = 0; + int offset = 0; + char *index_path; char *query; char *attr; + char *order; - /* stuff done only on the first call of the function */ - if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; - - /* create a function context for cross-call persistence */ - funcctx = SRF_FIRSTCALL_INIT(); - - /* switch to memory context appropriate for multiple function calls */ - oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - /* take arguments from function */ - - /* index path */ - if (PG_ARGISNULL(0)) { - elog(ERROR, "index path can't be null"); - SRF_RETURN_DONE(funcctx); - } - index_path = _textout(PG_GETARG_TEXT_P(0)); - /* query string */ - if (PG_ARGISNULL(0)) { - query = ""; - } else { - query = _textout(PG_GETARG_TEXT_P(1)); - } + /* only allow 1D input array */ + if (attr_ndims == 1) + { + ncols = attr_dim_counts[0]; + } + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid input array"), + errdetail("Input array must have 1 dimension"))); + + /* check to see if caller supports us returning a tuplestore */ + if (!rsinfo || !(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("materialize mode required, but it is not " \ + "allowed in this context"))); - /* atribute filter */ - if (PG_ARGISNULL(2)) { - attr = ""; - } else { - attr = _textout(PG_GETARG_TEXT_P(2)); - } + /* get info about element type needed to construct the array */ + get_typlenbyvalalign(attr_element_type, &attr_len, &attr_byval, &attr_align); - /* limit */ - if (PG_ARGISNULL(3)) { - limit = 0; - } else { - limit = PG_GETARG_INT32(3); - } + /* get the requested return tuple description */ + tupdesc = rsinfo->expectedDesc; + rsinfo_ncols = tupdesc->natts; - /* offset */ - if (PG_ARGISNULL(4)) { - offset = 0; - } else { - offset = PG_GETARG_INT32(4); - } + /* + * The requested tuple description better match up with the array + * we were given. + */ + if (rsinfo_ncols != ncols) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid input array"), + errdetail("Number of elements in array must match number of query specified columns."))); + /* OK, use it */ + attinmeta = TupleDescGetAttInMetadata(tupdesc); - /* open the database */ - elog(DEBUG1, "pgest: est_db_open(%s)", index_path); - - if(!(db = est_db_open(index_path, ESTDBREADER, &ecode))){ - elog(ERROR, "est_db_open: can't open %s [%d]: %s", index_path, ecode, est_err_msg(ecode)); - SRF_RETURN_DONE(funcctx); - } - - elog(INFO, "pgest: query[%s] attr[%s] limit %d offset %d", query, (PG_ARGISNULL(2) ? "NULL" : attr), limit, offset); - - /* create a search condition object */ - if (!(cond = est_cond_new())) { - elog(INFO, "pgest: est_cond_new failed"); - SRF_RETURN_DONE(funcctx); - } - - /* set the search phrase to the search condition object */ - if (! PG_ARGISNULL(1) && strlen(query) > 0) - est_cond_set_phrase(cond, query); - - /* minimum valid attribute length is 10: @a STREQ a */ - if (! PG_ARGISNULL(2) && strlen(attr) >= 10) { - elog(INFO,"est_cond_add_attr(%s)", attr); - est_cond_add_attr(cond, attr); - } + /* Now go to work */ + rsinfo->returnMode = SFRM_Materialize; - /* get the result of search */ - est_result = est_db_search(db, cond, &resnum, NULL); - - /* total number of tuples to be returned */ - if (limit && limit < resnum) { - funcctx->max_calls = limit - offset; - } else { - funcctx->max_calls = resnum - offset; - } + per_query_ctx = fcinfo->flinfo->fn_mcxt; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + /* initialize our tuplestore */ + tupstore = tuplestore_begin_heap(true, false, SortMem); + + + /* take rest of arguments from function */ + + /* index path */ + if (PG_ARGISNULL(0)) { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("index path can't be null"), + errdetail("Index path must be valid full path to HyperEstraier index"))); + } + index_path = _textout(PG_GETARG_TEXT_P(0)); + + /* query string */ + if (PG_ARGISNULL(1)) { + query = ""; + } else { + query = _textout(PG_GETARG_TEXT_P(1)); + } - /* check if results exists */ - if ( 0 == funcctx->max_calls ) - elog(INFO, "pgest: no results for: %s", query ); + /* atribute filter */ + if (PG_ARGISNULL(2)) { + attr = ""; + } else { + attr = _textout(PG_GETARG_TEXT_P(2)); + } + + /* sort order */ + if (PG_ARGISNULL(3)) { + order = ""; + } else { + order = _textout(PG_GETARG_TEXT_P(3)); + } - elog(DEBUG1, "pgest: found %d hits for %s", resnum, query); - /* Build a tuple description for a __pgest tuple */ - tupdesc = RelationNameGetTupleDesc("__pgest"); + /* limit */ + if (PG_ARGISNULL(4)) { + limit = 0; + } else { + limit = PG_GETARG_INT32(4); + } - /* allocate a slot for a tuple with this tupdesc */ - slot = TupleDescGetSlot(tupdesc); + /* offset */ + if (PG_ARGISNULL(5)) { + offset = 0; + } else { + offset = PG_GETARG_INT32(5); + } - /* assign slot to function context */ - funcctx->slot = slot; - /* - * generate attribute metadata needed later to produce tuples from raw - * C strings - */ - attinmeta = TupleDescGetAttInMetadata(tupdesc); - funcctx->attinmeta = attinmeta; + /* open the database */ + elog(DEBUG1, "pgest_attr: est_db_open(%s)", index_path); + + if(!(db = est_db_open(index_path, ESTDBREADER, &ecode))){ + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("est_db_open: can't open %s: %d", index_path, ecode), + errdetail(est_err_msg(ecode)))); + } + + elog(DEBUG1, "pgest_attr: query[%s] attr[%s] limit %d offset %d", query, (PG_ARGISNULL(2) ? "NULL" : attr), limit, offset); + + /* create a search condition object */ + if (!(cond = est_cond_new())) { + ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED), + errmsg("pgest_attr: est_cond_new failed"))); + } + + /* set the search phrase to the search condition object */ + if (! PG_ARGISNULL(1) && strlen(query) > 0) + est_cond_set_phrase(cond, query); + + /* minimum valid attribute length is 10: @a STREQ a */ + if (! PG_ARGISNULL(2) && strlen(attr) >= 10) { + elog(DEBUG1,"attributes: %s", attr); + char *curr_attr; + curr_attr = strtok(attr, ATTR_DELIMITER); + while (curr_attr) { + elog(DEBUG1,"est_cond_add_attr(%s)", curr_attr); + est_cond_add_attr(cond, curr_attr); + curr_attr = strtok(NULL, ATTR_DELIMITER); + } + } - MemoryContextSwitchTo(oldcontext); + /* set the search phrase to the search condition object */ + if (! PG_ARGISNULL(3) && strlen(order) > 0) { + elog(DEBUG1,"est_cond_set_order(%s)", order); + est_cond_set_order(cond, order); + } - elog(DEBUG1, "SRF_IS_FIRSTCALL done"); + if (limit) { + elog(DEBUG1,"est_cond_set_max(%d)", limit + offset); + est_cond_set_max(cond, limit + offset); } - /* stuff done on every call of the function */ - funcctx = SRF_PERCALL_SETUP(); + /* get the result of search */ + est_result = est_db_search(db, cond, &resnum, NULL); - call_cntr = funcctx->call_cntr; - max_calls = funcctx->max_calls; - slot = funcctx->slot; - attinmeta = funcctx->attinmeta; + /* check if results exists */ + if ( 0 == resnum ) { + elog(INFO, "pgest_attr: no results for: %s", query ); + } - if (limit && call_cntr > limit - 1) { - elog(INFO, "call_cntr: %d limit: %d", call_cntr, limit); - SRF_RETURN_DONE(funcctx); + /* total number of tuples to be returned */ + if (limit && limit < resnum) { + nrows = limit; + } else { + nrows = resnum - offset; } - if (call_cntr < max_calls) { - char **values; - HeapTuple tuple; - Datum result; - elog(DEBUG1, "pgest: loop count %d", call_cntr); + elog(DEBUG1, "pgest_attr: found %d hits for %s", resnum, query); - if (! est_result) { - elog(ERROR, "pgest: no estraier results"); - SRF_RETURN_DONE(funcctx); - } - - /* - * Prepare a values array for storage in our slot. - * This should be an array of C strings which will - * be processed later by the type input functions. - */ + values = (char **) palloc(ncols * sizeof(char *)); - if (doc = est_db_get_doc(db, est_result[call_cntr + offset], 0)) { - + for (i = 0; i < nrows; i++) + { + + /* get result from estraier */ + if (! ( doc = est_db_get_doc(db, est_result[i + offset], 0)) ) { + elog(INFO, "pgest_attr: can't find result %d", i + offset); + } else { elog(DEBUG1, "URI: %s\n Title: %s\n", est_doc_attr(doc, "@uri"), est_doc_attr(doc, "@title") ); + } - values = (char **) palloc(4 * sizeof(char *)); + /* iterate over results */ + for (j = 0; j < ncols; j++) + { + bool isnull; -// values[0] = (char *) palloc(strlen(_estval) * sizeof(char)); + /* array value of this position */ + indx[0] = j + attr_dim_lower_bounds[0]; - values[0] = (char *) attr2text(doc,"@id"); - values[1] = (char *) attr2text(doc,"@uri"); - values[2] = (char *) attr2text(doc,"@title"); - values[3] = (char *) attr2text(doc,"@size"); - - /* destloy the document object */ - elog(DEBUG2, "est_doc_delete"); - est_doc_delete(doc); - } else { - elog(INFO, "no result from estraier"); - values[0] = DatumGetCString( "" ); - values[1] = DatumGetCString( "" ); - values[2] = DatumGetCString( "" ); - values[3] = DatumGetCString( "" ); + dvalue = array_ref(attr_arr, attr_ndims, indx, -1, attr_len, attr_byval, attr_align, &isnull); + + if (!isnull && doc) + values[j] = DatumGetCString( + attr2text(doc, + (char *)DirectFunctionCall1(textout, dvalue) + )); + else + values[j] = NULL; } + /* construct the tuple */ + tuple = BuildTupleFromCStrings(attinmeta, values); + /* now store it */ + tuplestore_puttuple(tupstore, tuple); - elog(DEBUG2, "build tuple"); - /* build a tuple */ - tuple = BuildTupleFromCStrings(attinmeta, values); + /* delete estraier document object */ + if (doc) est_doc_delete(doc); + } - elog(DEBUG2, "make tuple into datum"); - /* make the tuple into a datum */ - result = TupleGetDatum(slot, tuple); + tuplestore_donestoring(tupstore); + rsinfo->setResult = tupstore; - elog(DEBUG2, "cleanup"); - /* clean up ? */ -/* - pfree(values[0]); - pfree(values[1]); - pfree(values[2]); - pfree(values[3]); - pfree(values); -*/ - - elog(DEBUG2, "cleanup over"); - - SRF_RETURN_NEXT(funcctx, result); - } else { - elog(DEBUG1, "loop over"); + /* + * SFRM_Materialize mode expects us to return a NULL Datum. The actual + * tuples are in our tuplestore and passed back through + * rsinfo->setResult. rsinfo->setDesc is set to the tuple description + * that we actually used to build our tuples with, so the caller can + * verify we did what it was expecting. + */ + rsinfo->setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); - if(!est_db_close(db, &ecode)){ - elog(INFO, "est_db_close error: %s", est_err_msg(ecode)); - } + est_cond_delete(cond); - /* do when there is no more left */ - SRF_RETURN_DONE(funcctx); + if(!est_db_close(db, &ecode)){ + ereport(ERROR, (errcode(ERRCODE_IO_ERROR), + errmsg("est_db_close: %d", ecode), + errdetail(est_err_msg(ecode)))); } + + return (Datum) 0; } -/* work in progress */ -PG_FUNCTION_INFO_V1(pgest2); -Datum pgest2(PG_FUNCTION_ARGS) + +/* make text var from attr */ +char *attr2text(ESTDOC *doc, char *attr) { + char *val; + const char *attrval; + int len; + int attrlen; + + if (! doc) return (Datum) NULL; + + elog(DEBUG1, "doc: %08x, attr: %s", doc, attr); + + if ( (attrval = est_doc_attr(doc, attr)) && (attrlen = strlen(attrval)) ) { + val = (char *) palloc(attrlen * sizeof(char)); + } else { + return (Datum) NULL; + } + + len = strlen(attrval); + elog(DEBUG1, "attr2text(%s) = '%s' %d bytes", attr, attrval, len); + + len++; + len *= sizeof(char); + + elog(DEBUG2, "palloc(%d)", len); + + val = palloc(len); + + memset(val, 0, len); + strncpy(val, attrval, len); + + elog(DEBUG2, "val=%s", val); + + return val; +} + +/* + * variation on theme: use node API which doesn't open index on + * every query which is much faster for large indexes + * + */ + +/* select * from pgest( */ +#define _arg_node_uri 0 +#define _arg_login 1 +#define _arg_passwd 2 +#define _arg_depth 3 +#define _arg_query 4 +#define _arg_attr 5 +#define _arg_order 6 +#define _arg_limit 7 +#define _arg_offset 8 +#define _arg_attr_array 9 +/* as (foo text, ... ); */ + + +PG_FUNCTION_INFO_V1(pgest_node); +Datum pgest_node(PG_FUNCTION_ARGS) { - int nrows = 3; - int16 typlen; - bool typbyval; - char typalign; + ArrayType *attr_arr = PG_GETARG_ARRAYTYPE_P(_arg_attr_array); + Oid attr_element_type = ARR_ELEMTYPE(attr_arr); + int attr_ndims = ARR_NDIM(attr_arr); + int *attr_dim_counts = ARR_DIMS(attr_arr); + int *attr_dim_lower_bounds = ARR_LBOUND(attr_arr); + int ncols = 0; + int nrows = 0; + int indx[MAXDIM]; + int16 attr_len; + bool attr_byval; + char attr_align; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; AttInMetadata *attinmeta; TupleDesc tupdesc; - Tuplestorestate *tupstore = NULL; + Tuplestorestate *tupstore = NULL; HeapTuple tuple; MemoryContext per_query_ctx; MemoryContext oldcontext; Datum dvalue; char **values; - int ncols; + int rsinfo_ncols; int i, j; + /* estvars */ + ESTNODE *node; + ESTCOND *cond; + ESTNODERES *nres; + ESTRESDOC *rdoc; + const CBLIST *texts; + int resnum = 0; + int limit = 0; + int offset = 0; + int depth = 0; + char *node_url; + char *user, *passwd; + char *query; + char *attr; + char *order; + + + /* only allow 1D input array */ + if (attr_ndims == 1) + { + ncols = attr_dim_counts[0]; + } + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid input array"), + errdetail("Input array must have 1 dimension"))); + /* check to see if caller supports us returning a tuplestore */ if (!rsinfo || !(rsinfo->allowedModes & SFRM_Materialize)) ereport(ERROR, @@ -288,14 +430,23 @@ errmsg("materialize mode required, but it is not " \ "allowed in this context"))); + /* get info about element type needed to construct the array */ + get_typlenbyvalalign(attr_element_type, &attr_len, &attr_byval, &attr_align); + /* get the requested return tuple description */ tupdesc = rsinfo->expectedDesc; - ncols = tupdesc->natts; + rsinfo_ncols = tupdesc->natts; /* * The requested tuple description better match up with the array * we were given. */ + if (rsinfo_ncols != ncols) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid input array"), + errdetail("Number of elements in array must match number of query specified columns."))); + /* OK, use it */ attinmeta = TupleDescGetAttInMetadata(tupdesc); @@ -308,19 +459,187 @@ /* initialize our tuplestore */ tupstore = tuplestore_begin_heap(true, false, SortMem); + + /* take rest of arguments from function */ + + /* node URL */ + if (PG_ARGISNULL(_arg_node_uri)) { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("node URL can't be null"), + errdetail("Node URL must be valid URL to HyperEstraier node"))); + } + node_url = _textout(PG_GETARG_TEXT_P(_arg_node_uri)); + + /* login and password */ + if (PG_ARGISNULL(_arg_login) || PG_ARGISNULL(_arg_passwd)) { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("username and password can't be NULL"), + errdetail("You must specify valid username and password to HyperEstraier node"))); + } + user = _textout(PG_GETARG_TEXT_P(_arg_login)); + passwd = _textout(PG_GETARG_TEXT_P(_arg_passwd)); + + /* depth of search */ + if (PG_ARGISNULL(_arg_depth)) { + depth = 0; + } else { + depth = PG_GETARG_INT32(_arg_depth); + } + + /* query string */ + if (PG_ARGISNULL(_arg_query)) { + query = ""; + } else { + query = _textout(PG_GETARG_TEXT_P(_arg_query)); + } + + /* atribute filter */ + if (PG_ARGISNULL(_arg_attr)) { + attr = ""; + } else { + attr = _textout(PG_GETARG_TEXT_P(_arg_attr)); + } + + /* sort order */ + if (PG_ARGISNULL(_arg_order)) { + order = ""; + } else { + order = _textout(PG_GETARG_TEXT_P(_arg_order)); + } + + + /* limit */ + if (PG_ARGISNULL(_arg_limit)) { + limit = 0; + } else { + limit = PG_GETARG_INT32(_arg_limit); + } + + /* offset */ + if (PG_ARGISNULL(_arg_offset)) { + offset = 0; + } else { + offset = PG_GETARG_INT32(_arg_offset); + } + + /* initialize the network environment */ + if(!est_init_net_env()){ + ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED), + errmsg("pgest_node: can't create network enviroment"))); + } + + /* create the node connection object */ + elog(DEBUG1, "pgest_node: est_node_new(%s) as %s", node_url, user); + node = est_node_new(node_url); + est_node_set_auth(node, user, passwd); + + 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); + + /* create a search condition object */ + if (!(cond = est_cond_new())) { + ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED), + errmsg("pgest_node: est_cond_new failed"))); + } + + /* set the search phrase to the search condition object */ + if (! PG_ARGISNULL(_arg_query) && strlen(query) > 0) + est_cond_set_phrase(cond, query); + + /* minimum valid attribute length is 10: @a STREQ a */ + if (! PG_ARGISNULL(_arg_attr) && strlen(attr) >= 10) { + elog(DEBUG1,"attributes: %s", attr); + char *curr_attr; + curr_attr = strtok(attr, ATTR_DELIMITER); + while (curr_attr) { + elog(DEBUG1,"est_cond_add_attr(%s)", curr_attr); + est_cond_add_attr(cond, curr_attr); + curr_attr = strtok(NULL, ATTR_DELIMITER); + } + } + + /* set the search phrase to the search condition object */ + if (! PG_ARGISNULL(_arg_order) && strlen(order) > 0) { + elog(DEBUG1,"est_cond_set_order(%s)", order); + est_cond_set_order(cond, order); + } + + if (limit) { + elog(DEBUG1,"est_cond_set_max(%d)", limit + offset); + est_cond_set_max(cond, limit + offset); + } + + /* get the result of search */ + nres = est_node_search(node, cond, depth); + + if (! nres) { + int status = est_node_status(node); + est_cond_delete(cond); + est_node_delete(node); + est_free_net_env(); + ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED), + errmsg("pgest_node: search failed, node status %d", status))); + } + + /* get number of results */ + resnum = est_noderes_doc_num(nres); + + /* check if results exists */ + if ( 0 == resnum ) { + elog(INFO, "pgest_node: no results for: %s", query ); + } + + /* total number of tuples to be returned */ + if (limit && limit < resnum) { + nrows = limit; + } else { + nrows = resnum - offset; + } + + + elog(DEBUG1, "pgest_node: found %d hits for %s", resnum, query); + + values = (char **) palloc(ncols * sizeof(char *)); for (i = 0; i < nrows; i++) { + + /* get result from estraier */ + if (! ( rdoc = est_noderes_get_doc(nres, i + offset) )) { + elog(INFO, "pgest_node: can't find result %d", i + offset); + } else { + elog(DEBUG1, "URI: %s\n Title: %s\n", + est_resdoc_attr(rdoc, "@uri"), + est_resdoc_attr(rdoc, "@title") + ); + } + + /* iterate over results */ for (j = 0; j < ncols; j++) { - values[j] = DatumGetCString( "foo" ); + bool isnull; + + /* array value of this position */ + indx[0] = j + attr_dim_lower_bounds[0]; + + dvalue = array_ref(attr_arr, attr_ndims, indx, -1, attr_len, attr_byval, attr_align, &isnull); + + if (!isnull && rdoc) + values[j] = DatumGetCString( + node_attr2text(rdoc, + (char *)DirectFunctionCall1(textout, dvalue) + )); + else + values[j] = NULL; } /* construct the tuple */ tuple = BuildTupleFromCStrings(attinmeta, values); /* now store it */ tuplestore_puttuple(tupstore, tuple); + } tuplestore_donestoring(tupstore); @@ -336,27 +655,40 @@ rsinfo->setDesc = tupdesc; MemoryContextSwitchTo(oldcontext); + /* delete the node result object */ + est_noderes_delete(nres); + + /* destroy the search condition object */ + est_cond_delete(cond); + + /* destroy the node object */ + est_node_delete(node); + + /* free the networking environment */ + est_free_net_env(); + return (Datum) 0; } - -/* make text var from attr */ -char *attr2text(ESTDOC *doc, char *attr) { +/* make text var from node attr */ +char *node_attr2text(ESTRESDOC *rdoc, char *attr) { char *val; const char *attrval; int len; int attrlen; - elog(DEBUG1, "doc: %08x, attr: %s", doc, attr); + if (! rdoc) return (Datum) NULL; - if ( (attrval = est_doc_attr(doc, attr)) && (attrlen = strlen(attrval)) ) { + elog(DEBUG1, "doc: %08x, attr: %s", rdoc, attr); + + if ( (attrval = est_resdoc_attr(rdoc, attr)) && (attrlen = strlen(attrval)) ) { val = (char *) palloc(attrlen * sizeof(char)); } else { return (Datum) NULL; } len = strlen(attrval); - elog(DEBUG1, "attr2text(%s) = '%s' %d bytes", attr, attrval, len); + elog(DEBUG1, "node_attr2text(%s) = '%s' %d bytes", attr, attrval, len); len++; len *= sizeof(char); @@ -373,30 +705,3 @@ return val; } -/* make integer variable from property */ -/* -char *prop2int(SW_RESULT sw_res, char *propname) { - char *val; - unsigned long prop; - int len; - - elog(DEBUG2, "prop2int(%s)", propname); - - prop = estResultPropertyULong( sw_res, propname ); - if (error_or_abort( est_handle )) return NULL; - - elog(DEBUG1, "prop2int(%s) = %lu", propname, prop); - - len = 128 * sizeof(char); - elog(DEBUG2, "palloc(%d)", len); - - val = palloc(len); - memset(val, 0, len); - - snprintf(val, len, "%lu", prop); - - elog(DEBUG2, "val=%s", val); - - return val; -} -*/