postgres/contrib/tsearch2/tsearch2.c

543 lines
12 KiB
C

/*-------------------------------------------------------------------------
*
* tsearch2.c
* Backwards-compatibility package for old contrib/tsearch2 API
*
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
* contrib/tsearch2/tsearch2.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/regproc.h"
#include "utils/syscache.h"
PG_MODULE_MAGIC;
static Oid current_dictionary_oid = InvalidOid;
static Oid current_parser_oid = InvalidOid;
/* insert given value at argument position 0 */
#define INSERT_ARGUMENT0(argument, isnull) \
do { \
int i; \
for (i = fcinfo->nargs; i > 0; i--) \
{ \
fcinfo->arg[i] = fcinfo->arg[i-1]; \
fcinfo->argnull[i] = fcinfo->argnull[i-1]; \
} \
fcinfo->arg[0] = (argument); \
fcinfo->argnull[0] = (isnull); \
fcinfo->nargs++; \
} while (0)
#define TextGetObjectId(infunction, text) \
DatumGetObjectId(DirectFunctionCall1(infunction, \
CStringGetDatum(text_to_cstring(text))))
#define UNSUPPORTED_FUNCTION(name) \
PG_FUNCTION_INFO_V1(name); \
Datum \
name(PG_FUNCTION_ARGS) \
{ \
ereport(ERROR, \
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\
errmsg("function %s is no longer supported", \
format_procedure(fcinfo->flinfo->fn_oid)), \
errhint("Switch to new tsearch functionality."))); \
/* keep compiler quiet */ \
PG_RETURN_NULL(); \
} \
extern int no_such_variable
static Oid GetCurrentDict(void);
static Oid GetCurrentParser(void);
PG_FUNCTION_INFO_V1(tsa_lexize_byname);
PG_FUNCTION_INFO_V1(tsa_lexize_bycurrent);
PG_FUNCTION_INFO_V1(tsa_set_curdict);
PG_FUNCTION_INFO_V1(tsa_set_curdict_byname);
PG_FUNCTION_INFO_V1(tsa_token_type_current);
PG_FUNCTION_INFO_V1(tsa_set_curprs);
PG_FUNCTION_INFO_V1(tsa_set_curprs_byname);
PG_FUNCTION_INFO_V1(tsa_parse_current);
PG_FUNCTION_INFO_V1(tsa_set_curcfg);
PG_FUNCTION_INFO_V1(tsa_set_curcfg_byname);
PG_FUNCTION_INFO_V1(tsa_to_tsvector_name);
PG_FUNCTION_INFO_V1(tsa_to_tsquery_name);
PG_FUNCTION_INFO_V1(tsa_plainto_tsquery_name);
PG_FUNCTION_INFO_V1(tsa_headline_byname);
PG_FUNCTION_INFO_V1(tsa_ts_stat);
PG_FUNCTION_INFO_V1(tsa_tsearch2);
PG_FUNCTION_INFO_V1(tsa_rewrite_accum);
PG_FUNCTION_INFO_V1(tsa_rewrite_finish);
/*
* List of unsupported functions
*
* The parser and dictionary functions are defined only so that the former
* contents of pg_ts_parser and pg_ts_dict can be loaded into the system,
* for ease of reference while creating the new tsearch configuration.
*/
UNSUPPORTED_FUNCTION(tsa_dex_init);
UNSUPPORTED_FUNCTION(tsa_dex_lexize);
UNSUPPORTED_FUNCTION(tsa_snb_en_init);
UNSUPPORTED_FUNCTION(tsa_snb_lexize);
UNSUPPORTED_FUNCTION(tsa_snb_ru_init_koi8);
UNSUPPORTED_FUNCTION(tsa_snb_ru_init_utf8);
UNSUPPORTED_FUNCTION(tsa_snb_ru_init);
UNSUPPORTED_FUNCTION(tsa_spell_init);
UNSUPPORTED_FUNCTION(tsa_spell_lexize);
UNSUPPORTED_FUNCTION(tsa_syn_init);
UNSUPPORTED_FUNCTION(tsa_syn_lexize);
UNSUPPORTED_FUNCTION(tsa_thesaurus_init);
UNSUPPORTED_FUNCTION(tsa_thesaurus_lexize);
UNSUPPORTED_FUNCTION(tsa_prsd_start);
UNSUPPORTED_FUNCTION(tsa_prsd_getlexeme);
UNSUPPORTED_FUNCTION(tsa_prsd_end);
UNSUPPORTED_FUNCTION(tsa_prsd_lextype);
UNSUPPORTED_FUNCTION(tsa_prsd_headline);
UNSUPPORTED_FUNCTION(tsa_reset_tsearch);
UNSUPPORTED_FUNCTION(tsa_get_covers);
/*
* list of redefined functions
*/
/* lexize(text, text) */
Datum
tsa_lexize_byname(PG_FUNCTION_ARGS)
{
text *dictname = PG_GETARG_TEXT_PP(0);
Datum arg1 = PG_GETARG_DATUM(1);
return DirectFunctionCall2(ts_lexize,
ObjectIdGetDatum(TextGetObjectId(regdictionaryin, dictname)),
arg1);
}
/* lexize(text) */
Datum
tsa_lexize_bycurrent(PG_FUNCTION_ARGS)
{
Datum arg0 = PG_GETARG_DATUM(0);
Oid id = GetCurrentDict();
return DirectFunctionCall2(ts_lexize,
ObjectIdGetDatum(id),
arg0);
}
/* set_curdict(int) */
Datum
tsa_set_curdict(PG_FUNCTION_ARGS)
{
Oid dict_oid = PG_GETARG_OID(0);
if (!SearchSysCacheExists(TSDICTOID,
ObjectIdGetDatum(dict_oid),
0, 0, 0))
elog(ERROR, "cache lookup failed for text search dictionary %u",
dict_oid);
current_dictionary_oid = dict_oid;
PG_RETURN_VOID();
}
/* set_curdict(text) */
Datum
tsa_set_curdict_byname(PG_FUNCTION_ARGS)
{
text *name = PG_GETARG_TEXT_PP(0);
Oid dict_oid;
dict_oid = get_ts_dict_oid(stringToQualifiedNameList(text_to_cstring(name)), false);
current_dictionary_oid = dict_oid;
PG_RETURN_VOID();
}
/* token_type() */
Datum
tsa_token_type_current(PG_FUNCTION_ARGS)
{
INSERT_ARGUMENT0(ObjectIdGetDatum(GetCurrentParser()), false);
return ts_token_type_byid(fcinfo);
}
/* set_curprs(int) */
Datum
tsa_set_curprs(PG_FUNCTION_ARGS)
{
Oid parser_oid = PG_GETARG_OID(0);
if (!SearchSysCacheExists(TSPARSEROID,
ObjectIdGetDatum(parser_oid),
0, 0, 0))
elog(ERROR, "cache lookup failed for text search parser %u",
parser_oid);
current_parser_oid = parser_oid;
PG_RETURN_VOID();
}
/* set_curprs(text) */
Datum
tsa_set_curprs_byname(PG_FUNCTION_ARGS)
{
text *name = PG_GETARG_TEXT_PP(0);
Oid parser_oid;
parser_oid = get_ts_parser_oid(stringToQualifiedNameList(text_to_cstring(name)), false);
current_parser_oid = parser_oid;
PG_RETURN_VOID();
}
/* parse(text) */
Datum
tsa_parse_current(PG_FUNCTION_ARGS)
{
INSERT_ARGUMENT0(ObjectIdGetDatum(GetCurrentParser()), false);
return ts_parse_byid(fcinfo);
}
/* set_curcfg(int) */
Datum
tsa_set_curcfg(PG_FUNCTION_ARGS)
{
Oid arg0 = PG_GETARG_OID(0);
char *name;
name = DatumGetCString(DirectFunctionCall1(regconfigout,
ObjectIdGetDatum(arg0)));
SetConfigOption("default_text_search_config", name,
PGC_USERSET, PGC_S_SESSION);
PG_RETURN_VOID();
}
/* set_curcfg(text) */
Datum
tsa_set_curcfg_byname(PG_FUNCTION_ARGS)
{
text *arg0 = PG_GETARG_TEXT_PP(0);
char *name;
name = text_to_cstring(arg0);
SetConfigOption("default_text_search_config", name,
PGC_USERSET, PGC_S_SESSION);
PG_RETURN_VOID();
}
/* to_tsvector(text, text) */
Datum
tsa_to_tsvector_name(PG_FUNCTION_ARGS)
{
text *cfgname = PG_GETARG_TEXT_PP(0);
Datum arg1 = PG_GETARG_DATUM(1);
Oid config_oid;
config_oid = TextGetObjectId(regconfigin, cfgname);
return DirectFunctionCall2(to_tsvector_byid,
ObjectIdGetDatum(config_oid), arg1);
}
/* to_tsquery(text, text) */
Datum
tsa_to_tsquery_name(PG_FUNCTION_ARGS)
{
text *cfgname = PG_GETARG_TEXT_PP(0);
Datum arg1 = PG_GETARG_DATUM(1);
Oid config_oid;
config_oid = TextGetObjectId(regconfigin, cfgname);
return DirectFunctionCall2(to_tsquery_byid,
ObjectIdGetDatum(config_oid), arg1);
}
/* plainto_tsquery(text, text) */
Datum
tsa_plainto_tsquery_name(PG_FUNCTION_ARGS)
{
text *cfgname = PG_GETARG_TEXT_PP(0);
Datum arg1 = PG_GETARG_DATUM(1);
Oid config_oid;
config_oid = TextGetObjectId(regconfigin, cfgname);
return DirectFunctionCall2(plainto_tsquery_byid,
ObjectIdGetDatum(config_oid), arg1);
}
/* headline(text, text, tsquery [,text]) */
Datum
tsa_headline_byname(PG_FUNCTION_ARGS)
{
Datum arg0 = PG_GETARG_DATUM(0);
Datum arg1 = PG_GETARG_DATUM(1);
Datum arg2 = PG_GETARG_DATUM(2);
Datum result;
Oid config_oid;
/* first parameter has to be converted to oid */
config_oid = DatumGetObjectId(DirectFunctionCall1(regconfigin,
CStringGetDatum(TextDatumGetCString(arg0))));
if (PG_NARGS() == 3)
result = DirectFunctionCall3(ts_headline_byid,
ObjectIdGetDatum(config_oid), arg1, arg2);
else
{
Datum arg3 = PG_GETARG_DATUM(3);
result = DirectFunctionCall4(ts_headline_byid_opt,
ObjectIdGetDatum(config_oid),
arg1, arg2, arg3);
}
return result;
}
/*
* tsearch2 version of update trigger
*
* We pass this on to the core trigger after inserting the default text
* search configuration name as the second argument. Note that this isn't
* a complete implementation of the original functionality; tsearch2 allowed
* transformation function names to be included in the list. However, that
* is deliberately removed as being a security risk.
*/
Datum
tsa_tsearch2(PG_FUNCTION_ARGS)
{
TriggerData *trigdata;
Trigger *trigger;
char **tgargs,
**tgargs_old;
int i;
Datum res;
/* Check call context */
if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */
elog(ERROR, "tsvector_update_trigger: not fired by trigger manager");
trigdata = (TriggerData *) fcinfo->context;
trigger = trigdata->tg_trigger;
if (trigger->tgnargs < 2)
elog(ERROR, "TSearch: format tsearch2(tsvector_field, text_field1,...)");
/* create space for configuration name */
tgargs = (char **) palloc((trigger->tgnargs + 1) * sizeof(char *));
tgargs[0] = trigger->tgargs[0];
for (i = 1; i < trigger->tgnargs; i++)
tgargs[i + 1] = trigger->tgargs[i];
tgargs[1] = pstrdup(GetConfigOptionByName("default_text_search_config",
NULL, false));
tgargs_old = trigger->tgargs;
trigger->tgargs = tgargs;
trigger->tgnargs++;
res = tsvector_update_trigger_byid(fcinfo);
/* restore old trigger data */
trigger->tgargs = tgargs_old;
trigger->tgnargs--;
pfree(tgargs[1]);
pfree(tgargs);
return res;
}
Datum
tsa_rewrite_accum(PG_FUNCTION_ARGS)
{
TSQuery acc;
ArrayType *qa;
TSQuery q;
QTNode *qex = NULL,
*subs = NULL,
*acctree = NULL;
bool isfind = false;
Datum *elemsp;
int nelemsp;
MemoryContext aggcontext;
MemoryContext oldcontext;
if (!AggCheckCallContext(fcinfo, &aggcontext))
elog(ERROR, "tsa_rewrite_accum called in non-aggregate context");
if (PG_ARGISNULL(0) || PG_GETARG_POINTER(0) == NULL)
{
acc = (TSQuery) MemoryContextAlloc(aggcontext, HDRSIZETQ);
SET_VARSIZE(acc, HDRSIZETQ);
acc->size = 0;
}
else
acc = PG_GETARG_TSQUERY(0);
if (PG_ARGISNULL(1) || PG_GETARG_POINTER(1) == NULL)
PG_RETURN_TSQUERY(acc);
else
qa = PG_GETARG_ARRAYTYPE_P_COPY(1);
if (ARR_NDIM(qa) != 1)
elog(ERROR, "array must be one-dimensional, not %d dimensions",
ARR_NDIM(qa));
if (ArrayGetNItems(ARR_NDIM(qa), ARR_DIMS(qa)) != 3)
elog(ERROR, "array must have three elements");
if (ARR_ELEMTYPE(qa) != TSQUERYOID)
elog(ERROR, "array must contain tsquery elements");
deconstruct_array(qa, TSQUERYOID, -1, false, 'i', &elemsp, NULL, &nelemsp);
q = DatumGetTSQuery(elemsp[0]);
if (q->size == 0)
{
pfree(elemsp);
PG_RETURN_POINTER(acc);
}
if (!acc->size)
{
if (VARSIZE(acc) > HDRSIZETQ)
{
pfree(elemsp);
PG_RETURN_POINTER(acc);
}
else
acctree = QT2QTN(GETQUERY(q), GETOPERAND(q));
}
else
acctree = QT2QTN(GETQUERY(acc), GETOPERAND(acc));
QTNTernary(acctree);
QTNSort(acctree);
q = DatumGetTSQuery(elemsp[1]);
if (q->size == 0)
{
pfree(elemsp);
PG_RETURN_POINTER(acc);
}
qex = QT2QTN(GETQUERY(q), GETOPERAND(q));
QTNTernary(qex);
QTNSort(qex);
q = DatumGetTSQuery(elemsp[2]);
if (q->size)
subs = QT2QTN(GETQUERY(q), GETOPERAND(q));
acctree = findsubquery(acctree, qex, subs, &isfind);
if (isfind || !acc->size)
{
/* pfree( acc ); do not pfree(p), because nodeAgg.c will */
if (acctree)
{
QTNBinary(acctree);
oldcontext = MemoryContextSwitchTo(aggcontext);
acc = QTN2QT(acctree);
MemoryContextSwitchTo(oldcontext);
}
else
{
acc = (TSQuery) MemoryContextAlloc(aggcontext, HDRSIZETQ);
SET_VARSIZE(acc, HDRSIZETQ);
acc->size = 0;
}
}
pfree(elemsp);
QTNFree(qex);
QTNFree(subs);
QTNFree(acctree);
PG_RETURN_TSQUERY(acc);
}
Datum
tsa_rewrite_finish(PG_FUNCTION_ARGS)
{
TSQuery acc = PG_GETARG_TSQUERY(0);
TSQuery rewrited;
if (acc == NULL || PG_ARGISNULL(0) || acc->size == 0)
{
rewrited = (TSQuery) palloc(HDRSIZETQ);
SET_VARSIZE(rewrited, HDRSIZETQ);
rewrited->size = 0;
}
else
{
rewrited = (TSQuery) palloc(VARSIZE(acc));
memcpy(rewrited, acc, VARSIZE(acc));
pfree(acc);
}
PG_RETURN_POINTER(rewrited);
}
/*
* Get Oid of current dictionary
*/
static Oid
GetCurrentDict(void)
{
if (current_dictionary_oid == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("no current dictionary"),
errhint("Execute SELECT set_curdict(...).")));
return current_dictionary_oid;
}
/*
* Get Oid of current parser
*
* Here, it seems reasonable to select the "default" parser if none has been
* set.
*/
static Oid
GetCurrentParser(void)
{
if (current_parser_oid == InvalidOid)
current_parser_oid = get_ts_parser_oid(stringToQualifiedNameList("pg_catalog.default"), false);
return current_parser_oid;
}