Search Postgresql Archives

Re: Fragments in tsearch2 headline

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I wrote a headline generation function for my app and I have attached
the patch (against the cvs head). It generates multiple contexts in
which the query appears. Essentially, it uses the cover function to
generate all covers, chooses smallest covers and stretches each
selected cover according to the chosen parameters. I think ideally
changes should be made to prsd_headline function but I couldn't
understand that segment of code well.

The sql interface is

headline_with_fragments(text parser, tsvector docvector, text doc,
tsquery queryin, int4 maxcoverSize, int4 mincoverSize, int4 maxWords)
 RETURNS text

This will generate headline that contain maxWords and each cover
stretched to maxcoverSize. It will not add any fragment with less than
mincoverSize.
I am running my app with maxcoverSize = 20, mincoverSize = 5, maxWords = 40.
So it shows roughly two fragments per query.

If Teoder or Oleg want to add this to main branch, I will be happy to
clean it up and test it better.

-Sushant.




On Oct 31, 2007 6:26 PM, Catalin Marinas <catalin.marinas@xxxxxxxxx> wrote:
> On 30/10/2007, Oleg Bartunov <oleg@xxxxxxxxxx> wrote:
> > ok, then you have to formalize many things - how long should be excerpts,
> > how much excerpts to show, etc. In tsearch2 we have get_covers() function,
> > which produces all excerpts like:
> >
> > =# select get_covers(to_tsvector('1 2 3 4 5 3 4 abc x y z 2 3'), '2&3'::tsquery);
> >                     get_covers
> > ------------------------------------------------
> >   1 {1 2 3 }1 4 5 {2 3 4 abc x y z {3 2 }2 3 }3
> > (1 row)
>
> This function generates the lexemes, so cannot be used directly, but
> it is probably a good starting point.
>
> > Once you formalize your requirements, you can look on it and adapt to your
> > needs (and share with people). I think it could be nice contrib module.
>
> It seems that Sushant already wants to implement this function. He
> would probably be faster than me :-) (I'm relatively new to db stuff).
> Since I mainly rely on whatever a web hosting company provides, I'll
> probably stick with a Python implementation outside the SQL query.
>
> Thanks for your answers.
>
> --
> Catalin
>
> ---------------------------(end of broadcast)---------------------------
>
> TIP 5: don't forget to increase your free space map settings
>
? GNUmakefile
? config.log
? config.status
? contrib/tsearch2/rank.h
? src/Makefile.global
? src/include/pg_config.h
? src/include/stamp-h
? src/interfaces/ecpg/include/ecpg_config.h
Index: contrib/tsearch2/rank.c
===================================================================
RCS file: /projects/cvsroot/pgsql/contrib/tsearch2/rank.c,v
retrieving revision 1.23
diff -c -r1.23 rank.c
*** contrib/tsearch2/rank.c	27 Feb 2007 23:48:06 -0000	1.23
--- contrib/tsearch2/rank.c	12 Nov 2007 03:28:20 -0000
***************
*** 21,26 ****
--- 21,27 ----
  #include "tsvector.h"
  #include "query.h"
  #include "common.h"
+ #include "rank.h"
  
  PG_FUNCTION_INFO_V1(rank);
  Datum		rank(PG_FUNCTION_ARGS);
***************
*** 419,433 ****
  }
  
  
- typedef struct
- {
- 	ITEM	  **item;
- 	int16		nitem;
- 	bool		needfree;
- 	uint8		wclass;
- 	int32		pos;
- }	DocRepresentation;
- 
  static int
  compareDocR(const void *a, const void *b)
  {
--- 420,425 ----
***************
*** 457,473 ****
  	}
  }
  
! typedef struct
! {
! 	int			pos;
! 	int			p;
! 	int			q;
! 	DocRepresentation *begin;
! 	DocRepresentation *end;
! }	Extention;
! 
! 
! static bool
  Cover(DocRepresentation * doc, int len, QUERYTYPE * query, Extention * ext)
  {
  	DocRepresentation *ptr;
--- 449,455 ----
  	}
  }
  
! bool
  Cover(DocRepresentation * doc, int len, QUERYTYPE * query, Extention * ext)
  {
  	DocRepresentation *ptr;
***************
*** 538,544 ****
  	return Cover(doc, len, query, ext);
  }
  
! static DocRepresentation *
  get_docrep(tsvector * txt, QUERYTYPE * query, int *doclen)
  {
  	ITEM	   *item = GETQUERY(query);
--- 520,526 ----
  	return Cover(doc, len, query, ext);
  }
  
! DocRepresentation *
  get_docrep(tsvector * txt, QUERYTYPE * query, int *doclen)
  {
  	ITEM	   *item = GETQUERY(query);
Index: contrib/tsearch2/ts_cfg.c
===================================================================
RCS file: /projects/cvsroot/pgsql/contrib/tsearch2/ts_cfg.c,v
retrieving revision 1.24
diff -c -r1.24 ts_cfg.c
*** contrib/tsearch2/ts_cfg.c	6 Apr 2007 04:21:41 -0000	1.24
--- contrib/tsearch2/ts_cfg.c	12 Nov 2007 03:28:20 -0000
***************
*** 646,648 ****
--- 646,715 ----
  	ts_error(NOTICE, "TSearch cache cleaned");
  	PG_RETURN_VOID();
  }
+ 
+ void
+ add_cover_to_hl(WParserInfo *prsobj, HLPRSTEXT *prs, QUERYTYPE *query, LexizeData *ldata, int4 *currentpos, int4 startpos, int4 endpos)
+ {
+ 	char      *lemm = NULL;
+ 	int4      lenlemm = 0;
+    	ParsedLex *lexs;
+   	int4       type, startHL = 0;
+    	TSLexeme  *norms;
+    	char      *coversep = " ... ";
+    	int4      coverseplen = strlen(coversep);
+    	int4      oldnumwords, newnumwords, i;
+    	if (*currentpos > endpos)
+    	{
+     	/* XXX - something wrong ... we have gone past the cover */
+     	return;
+    	}
+    	/* see if we need to add a cover seperator */
+    	if (*currentpos < startpos && startpos > 0)
+    	{
+        	hladdword(prs, coversep, coverseplen, 3);
+        	prs->words[prs->curwords - 1].in = 1;
+    	}
+ 
+   	do
+    	{
+        	type = DatumGetInt32(FunctionCall3(
+         									&(prsobj->getlexeme_info),
+ 											PointerGetDatum(prsobj->prs),
+ 											PointerGetDatum(&lemm),
+ 				 							PointerGetDatum(&lenlemm)));
+ 		LexizeAddLemm(ldata, type, lemm, lenlemm);
+        	do 
+        	{
+ 			if ((norms = LexizeExec(ldata, &lexs)) != NULL)
+            	{
+             	TSLexeme *ptr = norms;
+                	*currentpos += 1;
+                	while(ptr->lexeme)
+                	{
+ 					if (ptr->flags & TSL_ADDPOS)
+ 						*currentpos += 1;
+ 					ptr++;
+   				}       
+ 			}
+ 			// start check
+ 			if (!startHL && *currentpos >= startpos)
+ 				startHL = 1;
+ 
+ 		 	if (startHL)
+ 			{
+ 				oldnumwords = prs->curwords;
+ 				addHLParsedLex(prs, query, lexs, norms);
+ 				newnumwords = prs->curwords;
+ 				for (i = oldnumwords; i < newnumwords; i++)
+ 				{
+ 					prs->words[i].in = 1;
+ 					if (prs->words[i].item)
+ 						prs->words[i].selected = 1;
+ 				}
+ 			}
+ 		} while(norms && *currentpos < endpos);
+ 		if (*currentpos >= endpos)
+ 			break;
+ 	} while (type > 0);
+ }
+ 
Index: contrib/tsearch2/tsearch.sql.in
===================================================================
RCS file: /projects/cvsroot/pgsql/contrib/tsearch2/tsearch.sql.in,v
retrieving revision 1.21
diff -c -r1.21 tsearch.sql.in
*** contrib/tsearch2/tsearch.sql.in	11 Nov 2007 03:25:34 -0000	1.21
--- contrib/tsearch2/tsearch.sql.in	12 Nov 2007 03:28:20 -0000
***************
*** 571,576 ****
--- 571,582 ----
  AS 'MODULE_PATHNAME', 'rank_cd_def'
  LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE;
  
+ CREATE FUNCTION headline_with_fragments(text, tsvector, text, tsquery, int4, int4, int4)
+ RETURNS text 
+ AS 'MODULE_PATHNAME', 'headline_with_fragments'
+ LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE;
+ 
+ 
  CREATE OR REPLACE FUNCTION headline(oid, text, tsquery, text)
  RETURNS text
  AS 'MODULE_PATHNAME', 'headline'
Index: contrib/tsearch2/wparser.c
===================================================================
RCS file: /projects/cvsroot/pgsql/contrib/tsearch2/wparser.c,v
retrieving revision 1.13
diff -c -r1.13 wparser.c
*** contrib/tsearch2/wparser.c	2 Apr 2007 11:42:04 -0000	1.13
--- contrib/tsearch2/wparser.c	12 Nov 2007 03:28:20 -0000
***************
*** 18,23 ****
--- 18,27 ----
  #include "snmap.h"
  #include "common.h"
  
+ #include "rank.h"
+ #include "tsvector.h"
+ #include "dict.h"
+ 
  /*********top interface**********/
  
  static Oid	current_parser_id = InvalidOid;
***************
*** 609,611 ****
--- 613,799 ----
  				(PG_NARGS() > 2) ? PG_GETARG_DATUM(2) : PointerGetDatum(NULL)
  										));
  }
+ 
+ 
+        
+ /* headline generation 
+  * Input:  parser, tsvector of a doc, doc, tsquery, min size of an excerpt, max size of an excerpt, maxWords in a headline
+  * Output: multiple excerpts of doc that contains query words
+  */
+ 
+ struct coverpos{
+   	int4 startpos;
+    	int4 endpos;
+    	int4 in;
+ };
+ 
+ PG_FUNCTION_INFO_V1(headline_with_fragments);
+ Datum      headline_with_fragments(PG_FUNCTION_ARGS);
+ 
+ Datum
+ headline_with_fragments(PG_FUNCTION_ARGS)
+ {
+    	DocRepresentation* doc;
+    	Extention          ext;
+    	char*              textdata;
+    	int4               coverlen, doclen, textlen;
+    	int4               startpos = 0, endpos = 0, currentpos = 0;
+    	int4               numWords = 0;
+    	TSCfgInfo*         cfg;
+    	WParserInfo*       prsobj;
+    	text*              out;
+    	LexizeData         ldata;
+    	HLPRSTEXT          prs;
+   	int4               i, numcovers = 0, maxcovers = 32, maxstretch;
+    	int4               min, minI = 0;
+    	struct coverpos* covers = palloc(maxcovers*sizeof(struct coverpos));
+    	/* get the input parameters */
+    	text       *name     = PG_GETARG_TEXT_P(0);
+    	tsvector   *t        = (tsvector *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
+    	text       *in       = PG_GETARG_TEXT_P(2);
+    	QUERYTYPE  *query    = (QUERYTYPE *) PG_DETOAST_DATUM(PG_GETARG_DATUM(3));
+    	int4       maxcoverSize = PG_GETARG_INT32(4);
+    	int4       mincoverSize = PG_GETARG_INT32(5);
+    	int4       maxWords  = PG_GETARG_INT32(6);
+ 
+   	SET_FUNCOID();
+    	cfg = findcfg(name2id_cfg(name));
+    	prsobj = findprs(cfg->prs_id);
+ 
+    	textdata = VARDATA(in);
+    	textlen  = VARSIZE(in) - VARHDRSZ;
+    	/* start generating covers for the query */
+    	doc = get_docrep(t, query, &doclen);
+   	if (!doc)
+    	{
+ 		pfree(covers);
+   		PG_FREE_IF_COPY(name, 0);
+   		PG_FREE_IF_COPY(t, 1);
+   		PG_FREE_IF_COPY(in, 2);
+  		PG_FREE_IF_COPY(query, 3);
+  		/* cannot do anything */
+  		out = (text*) palloc(4);
+ 		SET_VARSIZE(out, 4);
+ 		PG_RETURN_POINTER(out);
+  	}
+ 
+ 
+   	memset(&prs, 0, sizeof(HLPRSTEXT));
+   	prs.lenwords = 32;
+    	prs.words = (HLWORD *) palloc(sizeof(HLWORD) * prs.lenwords);
+    	prs.startsel = "<b>";
+    	prs.stopsel  = "</b>";
+    	prs.startsellen = strlen(prs.startsel);
+    	prs.stopsellen = strlen(prs.stopsel);
+ 
+    	prsobj->prs = (void *) DatumGetPointer(
+    											FunctionCall2(
+ 															&(prsobj->start_info),
+ 															PointerGetDatum(textdata),
+ 															Int32GetDatum(textlen)));
+ 
+ 	LexizeInit(&ldata, cfg);
+ 
+    	MemSet(&ext, 0, sizeof(Extention));
+   	/* get all covers */
+    	while (Cover(doc, doclen, query, &ext))
+    	{
+   		if (numcovers >= maxcovers)
+ 		{
+ 	 		maxcovers *= 2;
+ 			covers     = repalloc(covers, sizeof(struct coverpos) * maxcovers);
+ 		}   
+ 		covers[numcovers].startpos = ext.p;
+ 		covers[numcovers].endpos   = ext.q;
+ 		covers[numcovers].in       = 0;
+  		numcovers ++;
+   	}
+    	/* choose best covers */
+    	while (maxWords - numWords > mincoverSize)
+    	{
+ 		min = 9999999;/* XXX - will not display headlines that exceed 9999999 */
+  		for (i = 0; i < numcovers; i ++)
+ 		{
+ 			coverlen = covers[i].endpos - covers[i].startpos + 1;
+ 			if (!covers[i].in && min > coverlen)
+ 			{
+ 				min  = coverlen; 
+ 				minI = i;
+ 			}   
+ 		}
+ 		if (min < 9999999)
+ 		{
+ 			covers[minI].in = 1;
+ 			/* adjust the size of cover  
+ 			* if maxcoverSize >= len 
+ 			*      then headline from ext.p - (maxcoverSize-len)/2 to ext.q + (maxcoverSize-len) /2
+ 			* if maxcoverSize < len 
+ 			*      then headline from ext.p to ext.p +  maxcoverSize 
+ 			*      (ensures starting lexeme is in the headline)
+ 			*/         
+ 			/* cut down endpos if it crosses maxWords */
+ 			startpos = covers[minI].startpos;
+ 			endpos   = covers[minI].endpos;
+ 			coverlen = endpos - startpos + 1;
+ 	
+ 			/* truncate the cover if it exceeds max words */
+ 			if(numWords + coverlen > maxWords)
+ 				endpos = startpos + maxWords - numWords;
+ 			else
+ 			{
+ 				if (maxcoverSize >= coverlen)
+ 				{
+ 					/* what is the max we can stretch: min of 
+ 					* 1. maxcoverSize
+ 					* 2. maxWords - numWords
+ 					*/
+ 					if (maxcoverSize > maxWords - numWords)
+ 						maxstretch = maxWords - numWords;
+ 					else    
+ 						maxstretch = maxcoverSize;
+ 	
+ 					/* divide the stretch on both sides of cover */
+ 					startpos -= (maxstretch - coverlen)/2;
+ 					endpos   += (maxstretch - coverlen)/2;
+ 					if (startpos < 1)
+ 						startpos = 1;
+ 					/* XXX - do we need to check whether endpos crosses the document 
+ 					* the other function would return if the document ends or the 
+ 					* endpos is reached.
+ 					* Dropping this check for time being 
+ 					*/	
+ 				}       
+ 				else if (maxcoverSize < coverlen)
+ 					endpos   = startpos + maxcoverSize;
+ 			}
+ 			covers[minI].startpos = startpos;
+ 			covers[minI].endpos   = endpos;
+ 			numWords += endpos - startpos + 1;
+  		}
+        	else 
+ 			break;
+   	}   
+ 
+    	/* start rendering the headline */
+    	numWords = 0;
+ 	for (i = 0; i < numcovers && numWords < maxWords; i++)
+ 	{
+ 		if (covers[i].in)
+ 			add_cover_to_hl(prsobj, &prs, query, &ldata, &currentpos, covers[i].startpos, covers[i].endpos);
+ 	}
+    
+    	FunctionCall1(
+ 					&(prsobj->end_info),
+ 					PointerGetDatum(prsobj->prs)
+ 				);
+    
+    	out = genhl(&prs);
+    	/* clean up */
+   	pfree(covers);
+    	PG_FREE_IF_COPY(name, 0);
+    	PG_FREE_IF_COPY(t, 1);
+    	PG_FREE_IF_COPY(in, 2);
+    	PG_FREE_IF_COPY(query, 3);
+ 	
+    	PG_RETURN_POINTER(out);
+ }
---------------------------(end of broadcast)---------------------------
TIP 6: explain analyze is your friend

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Postgresql Jobs]     [Postgresql Admin]     [Postgresql Performance]     [Linux Clusters]     [PHP Home]     [PHP on Windows]     [Kernel Newbies]     [PHP Classes]     [PHP Books]     [PHP Databases]     [Postgresql & PHP]     [Yosemite]
  Powered by Linux