/* * integrate swish-e into PostgreSQL * * Dobrica Pavlinusic 2005-02-18 * * TODO: * - check null input using PG_ARGISNULL before using PG_GETARG_xxxx * - support composite type arguments * * NOTES: * - clear structures with memset to support hash indexes (who whould like * to create hash index on table returned from function?) * - number of returned rows is set by PostgreSQL evaluator, see: * http://archives.postgresql.org/pgsql-hackers/2005-02/msg00546.php * * Based on: * - C example from PostgreSQL documentation (BSD licence) * - swish-e example src/libtest.c (GPL) * - _textin/_textout from pgcurl.c (LGPL) * * This code is licenced under GPL */ #include "postgres.h" #include "fmgr.h" #include "funcapi.h" #include "utils/builtins.h" #include "utils/array.h" #include "miscadmin.h" #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))) SW_HANDLE swish_handle = NULL;/* Database handle */ SW_SEARCH search = NULL; /* search handle -- holds search parameters */ SW_RESULTS swish_results = NULL; /* results handle -- holds list of results */ SW_RESULT *sw_res = NULL; /* one row from swish-e results */ /* define PostgreSQL v1 function */ PG_FUNCTION_INFO_V1(pgswish); Datum pgswish(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; int call_cntr; int max_calls; TupleDesc tupdesc; TupleTableSlot *slot; AttInMetadata *attinmeta; char *index_path; char *query; /* stuff done only on the first call of the function */ if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; /* take arguments from function */ //index_path = _textout(PG_GETARG_TEXT_P(0)); index_path = _textout(PG_GETARG_TEXT_P(0)); query = _textout(PG_GETARG_TEXT_P(1)); /* 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); /* Send any errors or warnings to stderr (default is stdout) */ SwishErrorsToStderr(); elog(INFO, "pgswish: SwishInit(%s)", index_path); swish_handle = SwishInit( index_path ); if (! swish_handle) { elog(ERROR, "pgswish: can't open %s", index_path); SRF_RETURN_DONE(funcctx); } error_or_abort( swish_handle ); /* set ranking scheme. default is 0 */ SwishRankScheme( swish_handle, 0 ); error_or_abort( swish_handle ); elog(INFO, "pgswish: SwishQuery(%s)", query); /* Here's a short-cut to searching that creates a search object and searches at the same time */ swish_results = SwishQuery( swish_handle, query); error_or_abort( swish_handle ); /* total number of tuples to be returned */ funcctx->max_calls = SwishHits( swish_results ); /* check if results exists */ if ( 0 == funcctx->max_calls ) elog(INFO, "no results for: %s", query ); elog(INFO, "pgswish: SwishHits = %d", funcctx->max_calls); /* Build a tuple description for a __pgswish tuple */ tupdesc = RelationNameGetTupleDesc("__pgswish"); /* allocate a slot for a tuple with this tupdesc */ slot = TupleDescGetSlot(tupdesc); /* 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; MemoryContextSwitchTo(oldcontext); elog(INFO, "SRF_IS_FIRSTCALL done"); } /* stuff done on every call of the function */ funcctx = SRF_PERCALL_SETUP(); call_cntr = funcctx->call_cntr; max_calls = funcctx->max_calls; slot = funcctx->slot; attinmeta = funcctx->attinmeta; if (call_cntr < max_calls) { char **values; HeapTuple tuple; Datum result; elog(INFO, "pgswish: loop count %d", call_cntr); if (! swish_results) { elog(ERROR, "pgswish: no swish-e results"); SRF_RETURN_DONE(funcctx); } elog(DEBUG1, "pgswish: check for swish-e error"); error_or_abort( swish_handle ); /* * 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. */ sw_res = SwishNextResult( swish_results ); if (! sw_res) { elog(ERROR, "pgswish: swish-e sort result list: %d rows expected %d", call_cntr, max_calls - 1); SRF_RETURN_DONE(funcctx); } elog(INFO, "Path: %s\n Rank: %lu\n Size: %lu\n Title: %s\n Index: %s\n Modified: %s\n Record #: %lu\n File #: %lu\n\n", SwishResultPropertyStr ( sw_res, "swishdocpath" ), SwishResultPropertyULong ( sw_res, "swishrank" ), SwishResultPropertyULong ( sw_res, "swishdocsize" ), SwishResultPropertyStr ( sw_res, "swishtitle"), SwishResultPropertyStr ( sw_res, "swishdbfile" ), SwishResultPropertyStr ( sw_res, "swishlastmodified" ), SwishResultPropertyULong ( sw_res, "swishreccount" ), /* can figure this out in loop, of course */ SwishResultPropertyULong ( sw_res, "swishfilenum" ) ); values = (char **) palloc(4 * sizeof(char *)); values[0] = prop2int( sw_res, "swishrank" ); values[1] = prop2text( sw_res, "swishdocpath" ); values[2] = prop2text( sw_res, "swishtitle" ); values[3] = prop2int( sw_res, "swishdocsize" ); /* values[0] = (char *) palloc(16 * sizeof(char)); snprintf(values[0], 16, "%d", 1); values[1] = (char *) palloc(16 * sizeof(char)); snprintf(values[1], 16, "%d", 2); values[2] = (char *) palloc(16 * sizeof(char)); snprintf(values[2], 16, "%d", 3); values[3] = (char *) palloc(16 * sizeof(char)); snprintf(values[3], 16, "%d", 4); */ /* build a tuple */ tuple = BuildTupleFromCStrings(attinmeta, values); /* make the tuple into a datum */ result = TupleGetDatum(slot, tuple); /* clean up ? */ pfree(values[0]); pfree(values[1]); pfree(values[2]); pfree(values[3]); pfree(values); elog(INFO, "row: %s|%s|%s|%s",values[0],values[1],values[2],values[3]); SRF_RETURN_NEXT(funcctx, result); } else { elog(INFO, "loop over"); /* free swish object and close */ Free_Search_Object( search ); SwishClose( swish_handle ); /* do when there is no more left */ SRF_RETURN_DONE(funcctx); } } /* work in progress */ PG_FUNCTION_INFO_V1(pgswish2); Datum pgswish2(PG_FUNCTION_ARGS) { int ncols = 2; int nrows = 3; int16 typlen; bool typbyval; char typalign; 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; /* 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"))); /* get the requested return tuple description */ tupdesc = rsinfo->expectedDesc; rsinfo_ncols = tupdesc->natts; /* * The requested tuple description better match up with the array * we were given. */ elog(INFO, "rsinfo_ncols = %d, ncols = %d", rsinfo_ncols, ncols); /* OK, use it */ attinmeta = TupleDescGetAttInMetadata(tupdesc); /* Now go to work */ rsinfo->returnMode = SFRM_Materialize; per_query_ctx = fcinfo->flinfo->fn_mcxt; oldcontext = MemoryContextSwitchTo(per_query_ctx); /* initialize our tuplestore */ tupstore = tuplestore_begin_heap(true, false, SortMem); values = (char **) palloc(ncols * sizeof(char *)); for (i = 0; i < nrows; i++) { for (j = 0; j < ncols; j++) { values[j] = DatumGetCString( "foo" ); } /* construct the tuple */ tuple = BuildTupleFromCStrings(attinmeta, values); /* now store it */ tuplestore_puttuple(tupstore, tuple); } tuplestore_donestoring(tupstore); rsinfo->setResult = tupstore; /* * 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); return (Datum) 0; } /* make text var prom property */ char *prop2text(SW_RESULT sw_res, char *propname) { char *val; char *prop; int len; elog(DEBUG1, "prop2text(%s)", propname); prop = SwishResultPropertyStr( sw_res, propname ); error_or_abort( swish_handle ); len = strlen(prop); elog(INFO, "prop2text(%s) = '%s' %d bytes", propname, prop, len); len++; len *= sizeof(char); elog(DEBUG1, "palloc(%d)", len); val = palloc(len); memset(val, 0, len); strncpy(val, prop, len); elog(DEBUG1, "val=%s", val); return val; } /* make integer variable from property */ char *prop2int(SW_RESULT sw_res, char *propname) { char *val; unsigned long prop; int len; elog(DEBUG1, "prop2int(%s)", propname); prop = SwishResultPropertyULong( sw_res, propname ); error_or_abort( swish_handle ); elog(INFO, "prop2int(%s) = %lu", propname, prop); len = 128 * sizeof(char); elog(DEBUG1, "palloc(%d)", len); val = palloc(len); memset(val, 0, len); snprintf(val, len, "%lu", prop); elog(DEBUG1, "val=%s", val); return val; } /* * check if swish has returned error, and elog it. */ static void error_or_abort( SW_HANDLE swish_handle ) { if ( !SwishError( swish_handle ) ) return; /* print a message */ elog(ERROR, "pgswish error: Number [%d], Type [%s], Optional Message: [%s]\n", SwishError( swish_handle ), SwishErrorString( swish_handle ), SwishLastErrorMsg( swish_handle ) ); if ( search ) Free_Search_Object( search ); SwishClose( swish_handle ); }