summaryrefslogtreecommitdiff
path: root/contrib/tsearch/txtidx.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2001-10-12 23:19:09 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2001-10-12 23:19:09 +0000
commitb57705673d02483b6e20d8a4d55a8aa182055d08 (patch)
treecb974bb78b96509ab7b8e69f1f43d2bb9658adcf /contrib/tsearch/txtidx.c
parentc24216bea8ae43b251900d1ce6ca9250943c33e0 (diff)
downloadpostgresql-b57705673d02483b6e20d8a4d55a8aa182055d08.tar.gz
txtidx datatype for full text indexing with GiST.
From Oleg Bartunov and Teodor Sigaev.
Diffstat (limited to 'contrib/tsearch/txtidx.c')
-rw-r--r--contrib/tsearch/txtidx.c506
1 files changed, 506 insertions, 0 deletions
diff --git a/contrib/tsearch/txtidx.c b/contrib/tsearch/txtidx.c
new file mode 100644
index 0000000000..39a64437c5
--- /dev/null
+++ b/contrib/tsearch/txtidx.c
@@ -0,0 +1,506 @@
+/*
+ * In/Out definitions for txtidx type
+ * Internal structure:
+ * string of values, array of position lexem in string and it's length
+ * Teodor Sigaev <teodor@stack.net>
+ */
+#include "postgres.h"
+
+#include "access/gist.h"
+#include "access/itup.h"
+#include "utils/elog.h"
+#include "utils/palloc.h"
+#include "utils/builtins.h"
+#include "storage/bufpage.h"
+#include "executor/spi.h"
+#include "commands/trigger.h"
+
+#include "utils/pg_locale.h"
+
+#include <ctype.h> /* tolower */
+#include "txtidx.h"
+#include "query.h"
+
+#include "deflex.h"
+#include "parser.h"
+
+#include "morph.h"
+
+PG_FUNCTION_INFO_V1(txtidx_in);
+Datum txtidx_in(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(txtidx_out);
+Datum txtidx_out(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(txt2txtidx);
+Datum txt2txtidx(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(tsearch);
+Datum tsearch(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(txtidxsize);
+Datum txtidxsize(PG_FUNCTION_ARGS);
+
+/*
+ * in/out text index type
+ */
+static char *BufferStr;
+static int
+compareentry( const void * a, const void * b ) {
+ if ( ((WordEntry*)a)->len == ((WordEntry*)b)->len ) {
+ return strncmp(
+ &BufferStr[((WordEntry*)a)->pos],
+ &BufferStr[((WordEntry*)b)->pos],
+ ((WordEntry*)b)->len );
+ }
+ return ( ((WordEntry*)a)->len > ((WordEntry*)b)->len ) ? 1 : -1;
+}
+
+static int
+uniqueentry( WordEntry* a, int4 l, char *buf, int4 *outbuflen ) {
+ WordEntry *ptr, *res;
+
+ res = a;
+ *outbuflen = res->len;
+ if ( l == 1 )
+ return l;
+
+ ptr = a+1;
+ BufferStr = buf;
+ qsort((void*)a, l, sizeof(int4), compareentry );
+ *outbuflen = res->len;
+
+ while (ptr - a < l) {
+ if ( ! (ptr->len == res->len &&
+ strncmp(&buf[ ptr->pos ], &buf[ res->pos ],res->len) == 0 ) ) {
+ res++;
+ res->len = ptr->len;
+ res->pos = ptr->pos;
+ *outbuflen += res->len;
+
+ }
+ ptr++;
+ }
+ return res + 1 - a;
+}
+
+#define WAITWORD 1
+#define WAITENDWORD 2
+#define WAITNEXTCHAR 3
+#define WAITENDCMPLX 4
+
+#define RESIZEPRSBUF if ( state->curpos - state->word == state->len ) { \
+ int4 clen = state->curpos - state->word; \
+ state->len *= 2; \
+ state->word = (char*)repalloc( (void*)state->word, state->len ); \
+ state->curpos = state->word + clen; \
+}
+
+int4
+gettoken_txtidx( TI_IN_STATE *state ) {
+ int4 oldstate = 0;
+ state->curpos = state->word;
+ state->state = WAITWORD;
+
+ while( 1 ) {
+ if ( state->state == WAITWORD ) {
+ if ( *(state->prsbuf) == '\0' ) {
+ return 0;
+ } else if ( *(state->prsbuf) == '\'' ) {
+ state->state = WAITENDCMPLX;
+ } else if ( *(state->prsbuf) == '\\' ) {
+ state->state = WAITNEXTCHAR;
+ oldstate = WAITENDWORD;
+ } else if ( state->oprisdelim && ISOPERATOR( *(state->prsbuf) ) ) {
+ elog(ERROR, "Syntax error");
+ } else if ( *(state->prsbuf) != ' ' ) {
+ *(state->curpos) = *(state->prsbuf);
+ state->curpos++;
+ state->state = WAITENDWORD;
+ }
+ } else if ( state->state == WAITNEXTCHAR ) {
+ if ( *(state->prsbuf) == '\0' ) {
+ elog(ERROR,"There is no escaped character");
+ } else {
+ RESIZEPRSBUF;
+ *(state->curpos) = *(state->prsbuf);
+ state->curpos++;
+ state->state = oldstate;
+ }
+ } else if ( state->state == WAITENDWORD ) {
+ if ( *(state->prsbuf) == '\\' ) {
+ state->state = WAITNEXTCHAR;
+ oldstate = WAITENDWORD;
+ } else if ( *(state->prsbuf) == ' ' || *(state->prsbuf) == '\0' ||
+ ( state->oprisdelim && ISOPERATOR( *(state->prsbuf) ) ) ) {
+ RESIZEPRSBUF;
+ if ( state->curpos == state->word )
+ elog(ERROR, "Syntax error");
+ *(state->curpos) = '\0';
+ return 1;
+ } else {
+ RESIZEPRSBUF;
+ *(state->curpos) = *(state->prsbuf);
+ state->curpos++;
+ }
+ } else if ( state->state == WAITENDCMPLX ) {
+ if ( *(state->prsbuf) == '\'' ) {
+ RESIZEPRSBUF;
+ *(state->curpos) = '\0';
+ if ( state->curpos == state->word )
+ elog(ERROR, "Syntax error");
+ state->prsbuf++;
+ return 1;
+ } else if ( *(state->prsbuf) == '\\' ) {
+ state->state = WAITNEXTCHAR;
+ oldstate = WAITENDCMPLX;
+ } else if ( *(state->prsbuf) == '\0' ) {
+ elog(ERROR,"Syntax error");
+ } else {
+ RESIZEPRSBUF;
+ *(state->curpos) = *(state->prsbuf);
+ state->curpos++;
+ }
+ } else {
+ elog(ERROR, "Inner bug :(");
+ }
+ state->prsbuf++;
+ }
+
+ return 0;
+}
+
+Datum
+txtidx_in(PG_FUNCTION_ARGS) {
+ char *buf = (char*)PG_GETARG_POINTER(0);
+ TI_IN_STATE state;
+ WordEntry *arr;
+ int4 len=0, totallen = 64;
+ txtidx *in;
+ char *tmpbuf, *cur;
+ int4 i,buflen = 256;
+
+ state.prsbuf = buf;
+ state.len=32;
+ state.word = (char*)palloc( state.len );
+ state.oprisdelim = false;
+
+ arr = (WordEntry*)palloc( sizeof(WordEntry) * totallen );
+ cur = tmpbuf = (char*)palloc( buflen );
+ while( gettoken_txtidx( &state ) ) {
+ if ( len == totallen ) {
+ totallen *= 2;
+ arr = (WordEntry*)repalloc( (void*)arr, sizeof(int4)*totallen );
+ }
+ while ( cur-tmpbuf + state.curpos - state.word >= buflen ) {
+ int4 dist = cur-tmpbuf;
+ buflen *= 2;
+ tmpbuf = (char*)repalloc( (void*)tmpbuf, buflen );
+ cur = tmpbuf+dist;
+ }
+ if ( state.curpos - state.word > 0xffff )
+ elog(ERROR,"Word is too long");
+ arr[len].len = state.curpos - state.word;
+ if ( cur - tmpbuf > 0xffff )
+ elog(ERROR,"Too long value");
+ arr[len].pos = cur - tmpbuf;
+ memcpy( (void*)cur, (void*)state.word, arr[len].len );
+ cur += arr[len].len;
+ len++;
+ }
+ pfree(state.word);
+
+ if ( !len )
+ elog(ERROR,"Void value");
+
+ len = uniqueentry( arr, len, tmpbuf, &buflen );
+ totallen = CALCDATASIZE( len, buflen );
+ in = (txtidx*)palloc( totallen );
+ in->len = totallen;
+ in->size = len;
+ cur = STRPTR(in);
+ for(i=0;i<len;i++) {
+ memcpy( (void*)cur, (void*)&tmpbuf[ arr[i].pos ], arr[i].len );
+ arr[i].pos = cur - STRPTR(in);
+ cur += arr[i].len;
+ }
+ pfree(tmpbuf);
+ memcpy( (void*)ARRPTR(in), (void*)arr, sizeof(int4)*len );
+ pfree( arr );
+ PG_RETURN_POINTER( in );
+}
+
+Datum
+txtidxsize(PG_FUNCTION_ARGS) {
+ txtidx *in=(txtidx*)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(0)));
+ int4 ret = in->size;
+ PG_FREE_IF_COPY(in,0);
+ PG_RETURN_INT32( ret );
+}
+
+Datum
+txtidx_out(PG_FUNCTION_ARGS) {
+ txtidx *out=(txtidx*)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(0)));
+ char *outbuf;
+ int4 i,j,lenbuf = STRSIZE(out) + 1 /* \0 */ + out->size*2 /* '' */ + out->size - 1 /* space */;
+ WordEntry *ptr = ARRPTR(out);
+ char *curin, *curout;
+
+ curout = outbuf = (char*) palloc( lenbuf );
+ for(i=0;i<out->size;i++) {
+ curin = STRPTR(out) + ptr->pos;
+ if ( i!= 0 )
+ *curout++ = ' ';
+ *curout++ = '\'';
+ j = ptr->len;
+ while( j-- ) {
+ if ( *curin == '\'' ) {
+ int4 pos = curout - outbuf;
+ outbuf = (char*)repalloc((void*)outbuf, ++lenbuf );
+ curout = outbuf + pos;
+ *curout++ = '\\';
+ }
+ *curout++ = *curin++;
+ }
+ *curout++ = '\'';
+ ptr++;
+ }
+ outbuf[ lenbuf-1 ] = '\0';
+ PG_FREE_IF_COPY(out,0);
+ PG_RETURN_POINTER( outbuf );
+}
+
+typedef struct {
+ uint16 len;
+ char* word;
+} WORD;
+
+typedef struct {
+ WORD *words;
+ int4 lenwords;
+ int4 curwords;
+} PRSTEXT;
+
+/*
+ * Parse text to lexems
+ */
+static void
+parsetext( PRSTEXT *prs, char *buf, int4 buflen ) {
+ int type,lenlemm;
+ char *ptr,*ptrw;
+ char *lemm;
+
+ start_parse_str( buf, buflen );
+ while( (type=tsearch_yylex()) != 0 ) {
+ if ( prs->curwords == prs->lenwords ) {
+ prs->lenwords *= 2;
+ prs->words = (WORD*)repalloc( (void*)prs->words, prs->lenwords * sizeof(WORD) );
+ }
+ if ( tokenlen>0xffff ) {
+ end_parse();
+ elog(ERROR, "Word is too long");
+ }
+
+ lenlemm = tokenlen;
+ lemm = lemmatize( token, &lenlemm, type );
+
+ if ( ! lemm )
+ continue;
+
+ if ( lemm != token ) {
+ prs->words[ prs->curwords ].len = lenlemm;
+ prs->words[ prs->curwords ].word = lemm;
+ } else {
+ prs->words[ prs->curwords ].len = lenlemm;
+ ptrw = prs->words[ prs->curwords ].word = (char*)palloc( lenlemm );
+ ptr = token;
+ while( ptr-token < lenlemm ) {
+ *ptrw = tolower( (unsigned char) *ptr );
+ ptr++; ptrw++;
+ }
+ }
+ prs->curwords++;
+ }
+ end_parse();
+}
+
+static int
+compareWORD( const void * a, const void * b ) {
+ if ( ((WORD*)a)->len == ((WORD*)b)->len )
+ return strncmp(
+ ((WORD*)a)->word,
+ ((WORD*)b)->word,
+ ((WORD*)b)->len );
+ return ( ((WORD*)a)->len > ((WORD*)b)->len ) ? 1 : -1;
+}
+
+static int
+uniqueWORD( WORD* a, int4 l ) {
+ WORD *ptr, *res;
+
+ if ( l == 1 )
+ return l;
+
+ res = a;
+ ptr = a + 1;
+
+ qsort((void*)a, l, sizeof(WORD), compareWORD );
+
+ while (ptr - a < l) {
+ if ( ! (ptr->len == res->len &&
+ strncmp(ptr->word, res->word ,res->len) == 0 ) ) {
+ res++;
+ res->len = ptr->len;
+ res->word = ptr->word;
+ } else {
+ pfree(ptr->word);
+ }
+ ptr++;
+ }
+
+ return res + 1 - a;
+}
+
+/*
+ * make value of txtidx
+ */
+static txtidx *
+makevalue( PRSTEXT *prs ) {
+ int4 i, lenstr=0, totallen;
+ txtidx *in;
+ WordEntry *ptr;
+ char *str,*cur;
+
+ prs->curwords = uniqueWORD( prs->words, prs->curwords );
+ for(i=0;i<prs->curwords;i++)
+ lenstr += prs->words[i].len;
+
+ totallen = CALCDATASIZE( prs->curwords, lenstr );
+ in = (txtidx*)palloc( totallen );
+ in->len = totallen;
+ in->size = prs->curwords;
+
+ ptr = ARRPTR(in);
+ cur = str = STRPTR(in);
+ for(i=0;i<prs->curwords;i++) {
+ ptr->len = prs->words[i].len;
+ if ( cur-str > 0xffff )
+ elog(ERROR,"Value is too big");
+ ptr->pos = cur-str;
+ ptr++;
+ memcpy( (void*)cur, (void*)prs->words[i].word, prs->words[i].len );
+ pfree(prs->words[i].word);
+ cur += prs->words[i].len;
+ }
+ pfree(prs->words);
+ return in;
+}
+
+Datum
+txt2txtidx(PG_FUNCTION_ARGS) {
+ text *in = (text*)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(0)));
+ PRSTEXT prs;
+ txtidx *out = NULL;
+
+ prs.lenwords = 32;
+ prs.curwords = 0;
+ prs.words = (WORD*)palloc(sizeof(WORD)*prs.lenwords);
+
+ initmorph();
+ parsetext( &prs, VARDATA(in), VARSIZE(in) - VARHDRSZ );
+ PG_FREE_IF_COPY(in,0);
+
+ if ( prs.curwords ) {
+ out = makevalue( &prs );
+ PG_RETURN_POINTER( out );
+ }
+ pfree(prs.words);
+ PG_RETURN_NULL();
+}
+
+/*
+ * Trigger
+ */
+Datum
+tsearch(PG_FUNCTION_ARGS) {
+ TriggerData *trigdata;
+ Trigger *trigger;
+ Relation rel;
+ HeapTuple rettuple = NULL;
+ int numidxattr,i;
+ PRSTEXT prs;
+ Datum datum = (Datum)0;
+
+
+ if (!CALLED_AS_TRIGGER(fcinfo))
+ elog(ERROR, "TSearch: Not fired by trigger manager");
+
+ trigdata = (TriggerData *) fcinfo->context;
+ if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
+ elog(ERROR, "TSearch: Can't process STATEMENT events");
+ if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
+ elog(ERROR, "TSearch: Must be fired BEFORE event");
+
+ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+ rettuple = trigdata->tg_trigtuple;
+ else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ rettuple = trigdata->tg_newtuple;
+ else
+ elog(ERROR, "TSearch: Unknown event");
+
+ trigger = trigdata->tg_trigger;
+ rel = trigdata->tg_relation;
+
+ if ( trigger->tgnargs < 2 )
+ elog(ERROR,"TSearch: format tsearch(txtidx_field, text_field1,...)");
+
+ numidxattr = SPI_fnumber(rel->rd_att, trigger->tgargs[0]);
+ if ( numidxattr < 0 )
+ elog(ERROR,"TSearch: Can not find txtidx_field");
+
+ prs.lenwords = 32;
+ prs.curwords = 0;
+ prs.words = (WORD*)palloc(sizeof(WORD)*prs.lenwords);
+
+ initmorph();
+ /* find all words in indexable column */
+ for(i=1; i<trigger->tgnargs; i++) {
+ int4 numattr;
+ text *txt_toasted, *txt;
+ bool isnull;
+ Oid oidtype;
+
+ numattr = SPI_fnumber(rel->rd_att, trigger->tgargs[i]);
+ oidtype = SPI_gettypeid(rel->rd_att, numattr);
+ if ( numattr<0 || ( ! ( oidtype==TEXTOID || oidtype==VARCHAROID ) ) ) {
+ elog(NOTICE, "TSearch: can not find field '%s'", trigger->tgargs[i]);
+ continue;
+ }
+ txt_toasted = (text*)DatumGetPointer( SPI_getbinval(rettuple, rel->rd_att, numattr, &isnull ) );
+ if ( isnull )
+ continue;
+ txt = (text*)DatumGetPointer( PG_DETOAST_DATUM( PointerGetDatum ( txt_toasted ) ) );
+
+ parsetext( &prs, VARDATA(txt), VARSIZE(txt) - VARHDRSZ );
+ if ( txt != txt_toasted )
+ pfree(txt);
+ }
+
+ /* make txtidx value */
+ if (prs.curwords) {
+ datum = PointerGetDatum( makevalue( &prs ) );
+ rettuple = SPI_modifytuple( rel, rettuple, 1, &numidxattr,
+ &datum, NULL );
+ pfree(DatumGetPointer(datum));
+ } else {
+ char nulls = 'n';
+ pfree( prs.words );
+ rettuple = SPI_modifytuple( rel, rettuple, 1, &numidxattr,
+ &datum, &nulls );
+ }
+
+ if (rettuple == NULL)
+ elog(ERROR, "TSearch: %d returned by SPI_modifytuple", SPI_result);
+
+ return PointerGetDatum( rettuple );
+}
+