mirror of
https://github.com/postgres/postgres.git
synced 2025-07-08 11:42:09 +03:00
Simplify the syntax of CREATE/ALTER TEXT SEARCH DICTIONARY by treating the
init options of the template as top-level options in the syntax. This also makes ALTER a bit easier to use, since options can be replaced individually. I also made these statements verify that the tmplinit method will accept the new settings before they get stored; in the original coding you didn't find out about mistakes until the dictionary got invoked. Under the hood, init methods now get options as a List of DefElem instead of a raw text string --- that lets tsearch use existing options-pushing code instead of duplicating functionality.
This commit is contained in:
@ -9,12 +9,13 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/tsearchcmds.c,v 1.2 2007/08/21 21:24:00 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/tsearchcmds.c,v 1.3 2007/08/22 01:39:44 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
#include "miscadmin.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "access/heapam.h"
|
||||
#include "access/genam.h"
|
||||
@ -31,6 +32,8 @@
|
||||
#include "catalog/pg_ts_template.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "commands/defrem.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "parser/parse_func.h"
|
||||
#include "tsearch/ts_cache.h"
|
||||
#include "tsearch/ts_public.h"
|
||||
@ -86,7 +89,7 @@ get_ts_parser_func(DefElem *defel, int attnum)
|
||||
break;
|
||||
case Anum_pg_ts_parser_prsheadline:
|
||||
nargs = 3;
|
||||
typeId[1] = TEXTOID;
|
||||
typeId[1] = INTERNALOID;
|
||||
typeId[2] = TSQUERYOID;
|
||||
break;
|
||||
case Anum_pg_ts_parser_prslextype:
|
||||
@ -407,6 +410,53 @@ makeDictionaryDependencies(HeapTuple tuple)
|
||||
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
||||
}
|
||||
|
||||
/*
|
||||
* verify that a template's init method accepts a proposed option list
|
||||
*/
|
||||
static void
|
||||
verify_dictoptions(Oid tmplId, List *dictoptions)
|
||||
{
|
||||
HeapTuple tup;
|
||||
Form_pg_ts_template tform;
|
||||
Oid initmethod;
|
||||
|
||||
tup = SearchSysCache(TSTEMPLATEOID,
|
||||
ObjectIdGetDatum(tmplId),
|
||||
0, 0, 0);
|
||||
if (!HeapTupleIsValid(tup)) /* should not happen */
|
||||
elog(ERROR, "cache lookup failed for text search template %u",
|
||||
tmplId);
|
||||
tform = (Form_pg_ts_template) GETSTRUCT(tup);
|
||||
|
||||
initmethod = tform->tmplinit;
|
||||
|
||||
if (!OidIsValid(initmethod))
|
||||
{
|
||||
/* If there is no init method, disallow any options */
|
||||
if (dictoptions)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("text search template \"%s\" does not accept options",
|
||||
NameStr(tform->tmplname))));
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Copy the options just in case init method thinks it can scribble
|
||||
* on them ...
|
||||
*/
|
||||
dictoptions = copyObject(dictoptions);
|
||||
|
||||
/*
|
||||
* Call the init method and see if it complains. We don't worry about
|
||||
* it leaking memory, since our command will soon be over anyway.
|
||||
*/
|
||||
(void) OidFunctionCall1(initmethod, PointerGetDatum(dictoptions));
|
||||
}
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
}
|
||||
|
||||
/*
|
||||
* CREATE TEXT SEARCH DICTIONARY
|
||||
*/
|
||||
@ -419,7 +469,8 @@ DefineTSDictionary(List *names, List *parameters)
|
||||
Datum values[Natts_pg_ts_dict];
|
||||
char nulls[Natts_pg_ts_dict];
|
||||
NameData dname;
|
||||
int i;
|
||||
Oid templId = InvalidOid;
|
||||
List *dictoptions = NIL;
|
||||
Oid dictOid;
|
||||
Oid namespaceoid;
|
||||
AclResult aclresult;
|
||||
@ -434,18 +485,6 @@ DefineTSDictionary(List *names, List *parameters)
|
||||
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
|
||||
get_namespace_name(namespaceoid));
|
||||
|
||||
for (i = 0; i < Natts_pg_ts_dict; i++)
|
||||
{
|
||||
nulls[i] = ' ';
|
||||
values[i] = ObjectIdGetDatum(InvalidOid);
|
||||
}
|
||||
|
||||
namestrcpy(&dname, dictname);
|
||||
values[Anum_pg_ts_dict_dictname - 1] = NameGetDatum(&dname);
|
||||
values[Anum_pg_ts_dict_dictnamespace - 1] = ObjectIdGetDatum(namespaceoid);
|
||||
values[Anum_pg_ts_dict_dictowner - 1] = ObjectIdGetDatum(GetUserId());
|
||||
nulls[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
|
||||
|
||||
/*
|
||||
* loop over the definition list and extract the information we need.
|
||||
*/
|
||||
@ -455,42 +494,41 @@ DefineTSDictionary(List *names, List *parameters)
|
||||
|
||||
if (pg_strcasecmp(defel->defname, "template") == 0)
|
||||
{
|
||||
Oid templId;
|
||||
|
||||
templId = TSTemplateGetTmplid(defGetQualifiedName(defel), false);
|
||||
|
||||
values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(templId);
|
||||
nulls[Anum_pg_ts_dict_dicttemplate - 1] = ' ';
|
||||
}
|
||||
else if (pg_strcasecmp(defel->defname, "option") == 0)
|
||||
{
|
||||
char *opt = defGetString(defel);
|
||||
|
||||
if (pg_strcasecmp(opt, "null") != 0)
|
||||
{
|
||||
values[Anum_pg_ts_dict_dictinitoption - 1] =
|
||||
DirectFunctionCall1(textin, CStringGetDatum(opt));
|
||||
nulls[Anum_pg_ts_dict_dictinitoption - 1] = ' ';
|
||||
}
|
||||
}
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("text search dictionary parameter \"%s\" not recognized",
|
||||
defel->defname)));
|
||||
{
|
||||
/* Assume it's an option for the dictionary itself */
|
||||
dictoptions = lappend(dictoptions, defel);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Validation
|
||||
*/
|
||||
if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_dict_dicttemplate - 1])))
|
||||
if (!OidIsValid(templId))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("text search template is required")));
|
||||
|
||||
verify_dictoptions(templId, dictoptions);
|
||||
|
||||
/*
|
||||
* Looks good, insert
|
||||
*/
|
||||
memset(values, 0, sizeof(values));
|
||||
memset(nulls, ' ', sizeof(nulls));
|
||||
|
||||
namestrcpy(&dname, dictname);
|
||||
values[Anum_pg_ts_dict_dictname - 1] = NameGetDatum(&dname);
|
||||
values[Anum_pg_ts_dict_dictnamespace - 1] = ObjectIdGetDatum(namespaceoid);
|
||||
values[Anum_pg_ts_dict_dictowner - 1] = ObjectIdGetDatum(GetUserId());
|
||||
values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(templId);
|
||||
if (dictoptions)
|
||||
values[Anum_pg_ts_dict_dictinitoption - 1] =
|
||||
PointerGetDatum(serialize_deflist(dictoptions));
|
||||
else
|
||||
nulls[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
|
||||
|
||||
dictRel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
|
||||
|
||||
@ -652,6 +690,9 @@ AlterTSDictionary(AlterTSDictionaryStmt * stmt)
|
||||
Relation rel;
|
||||
Oid dictId;
|
||||
ListCell *pl;
|
||||
List *dictoptions;
|
||||
Datum opt;
|
||||
bool isnull;
|
||||
Datum repl_val[Natts_pg_ts_dict];
|
||||
char repl_null[Natts_pg_ts_dict];
|
||||
char repl_repl[Natts_pg_ts_dict];
|
||||
@ -673,41 +714,67 @@ AlterTSDictionary(AlterTSDictionaryStmt * stmt)
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
|
||||
NameListToString(stmt->dictname));
|
||||
|
||||
memset(repl_val, 0, sizeof(repl_val));
|
||||
memset(repl_null, ' ', sizeof(repl_null));
|
||||
memset(repl_repl, ' ', sizeof(repl_repl));
|
||||
/* deserialize the existing set of options */
|
||||
opt = SysCacheGetAttr(TSDICTOID, tup,
|
||||
Anum_pg_ts_dict_dictinitoption,
|
||||
&isnull);
|
||||
if (isnull)
|
||||
dictoptions = NIL;
|
||||
else
|
||||
dictoptions = deserialize_deflist(opt);
|
||||
|
||||
/*
|
||||
* NOTE: because we only support altering the option, not the template,
|
||||
* there is no need to update dependencies.
|
||||
* Modify the options list as per specified changes
|
||||
*/
|
||||
foreach(pl, stmt->options)
|
||||
{
|
||||
DefElem *defel = (DefElem *) lfirst(pl);
|
||||
ListCell *cell;
|
||||
ListCell *prev;
|
||||
ListCell *next;
|
||||
|
||||
if (pg_strcasecmp(defel->defname, "option") == 0)
|
||||
/*
|
||||
* Remove any matches ...
|
||||
*/
|
||||
prev = NULL;
|
||||
for (cell = list_head(dictoptions); cell; cell = next)
|
||||
{
|
||||
char *opt = defGetString(defel);
|
||||
DefElem *oldel = (DefElem *) lfirst(cell);
|
||||
|
||||
if (pg_strcasecmp(opt, "null") == 0)
|
||||
{
|
||||
repl_null[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
|
||||
}
|
||||
next = lnext(cell);
|
||||
if (pg_strcasecmp(oldel->defname, defel->defname) == 0)
|
||||
dictoptions = list_delete_cell(dictoptions, cell, prev);
|
||||
else
|
||||
{
|
||||
repl_val[Anum_pg_ts_dict_dictinitoption - 1] =
|
||||
DirectFunctionCall1(textin, CStringGetDatum(opt));
|
||||
repl_null[Anum_pg_ts_dict_dictinitoption - 1] = ' ';
|
||||
}
|
||||
repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = 'r';
|
||||
prev = cell;
|
||||
}
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("text search dictionary parameter \"%s\" not recognized",
|
||||
defel->defname)));
|
||||
|
||||
/*
|
||||
* and add new value if it's got one
|
||||
*/
|
||||
if (defel->arg)
|
||||
dictoptions = lappend(dictoptions, defel);
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate
|
||||
*/
|
||||
verify_dictoptions(((Form_pg_ts_dict) GETSTRUCT(tup))->dicttemplate,
|
||||
dictoptions);
|
||||
|
||||
/*
|
||||
* Looks good, update
|
||||
*/
|
||||
memset(repl_val, 0, sizeof(repl_val));
|
||||
memset(repl_null, ' ', sizeof(repl_null));
|
||||
memset(repl_repl, ' ', sizeof(repl_repl));
|
||||
|
||||
if (dictoptions)
|
||||
repl_val[Anum_pg_ts_dict_dictinitoption - 1] =
|
||||
PointerGetDatum(serialize_deflist(dictoptions));
|
||||
else
|
||||
repl_null[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
|
||||
repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = 'r';
|
||||
|
||||
newtup = heap_modifytuple(tup, RelationGetDescr(rel),
|
||||
repl_val, repl_null, repl_repl);
|
||||
|
||||
@ -715,6 +782,12 @@ AlterTSDictionary(AlterTSDictionaryStmt * stmt)
|
||||
|
||||
CatalogUpdateIndexes(rel, newtup);
|
||||
|
||||
/*
|
||||
* NOTE: because we only support altering the options, not the template,
|
||||
* there is no need to update dependencies. This might have to change
|
||||
* if the options ever reference inside-the-database objects.
|
||||
*/
|
||||
|
||||
heap_freetuple(newtup);
|
||||
ReleaseSysCache(tup);
|
||||
|
||||
@ -1941,3 +2014,265 @@ DropConfigurationMapping(AlterTSConfigurationStmt *stmt, HeapTuple tup)
|
||||
|
||||
heap_close(relMap, RowExclusiveLock);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Serialize dictionary options, producing a TEXT datum from a List of DefElem
|
||||
*
|
||||
* This is used to form the value stored in pg_ts_dict.dictinitoption.
|
||||
* For the convenience of pg_dump, the output is formatted exactly as it
|
||||
* would need to appear in CREATE TEXT SEARCH DICTIONARY to reproduce the
|
||||
* same options.
|
||||
*
|
||||
* Note that we assume that only the textual representation of an option's
|
||||
* value is interesting --- hence, non-string DefElems get forced to strings.
|
||||
*/
|
||||
text *
|
||||
serialize_deflist(List *deflist)
|
||||
{
|
||||
text *result;
|
||||
StringInfoData buf;
|
||||
ListCell *l;
|
||||
|
||||
initStringInfo(&buf);
|
||||
|
||||
foreach(l, deflist)
|
||||
{
|
||||
DefElem *defel = (DefElem *) lfirst(l);
|
||||
char *val = defGetString(defel);
|
||||
|
||||
appendStringInfo(&buf, "%s = ",
|
||||
quote_identifier(defel->defname));
|
||||
/* If backslashes appear, force E syntax to determine their handling */
|
||||
if (strchr(val, '\\'))
|
||||
appendStringInfoChar(&buf, ESCAPE_STRING_SYNTAX);
|
||||
appendStringInfoChar(&buf, '\'');
|
||||
while (*val)
|
||||
{
|
||||
char ch = *val++;
|
||||
|
||||
if (SQL_STR_DOUBLE(ch, true))
|
||||
appendStringInfoChar(&buf, ch);
|
||||
appendStringInfoChar(&buf, ch);
|
||||
}
|
||||
appendStringInfoChar(&buf, '\'');
|
||||
if (lnext(l) != NULL)
|
||||
appendStringInfo(&buf, ", ");
|
||||
}
|
||||
|
||||
result = CStringGetTextP(buf.data);
|
||||
pfree(buf.data);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Deserialize dictionary options, reconstructing a List of DefElem from TEXT
|
||||
*
|
||||
* This is also used for prsheadline options, so for backward compatibility
|
||||
* we need to accept a few things serialize_deflist() will never emit:
|
||||
* in particular, unquoted and double-quoted values.
|
||||
*/
|
||||
List *
|
||||
deserialize_deflist(Datum txt)
|
||||
{
|
||||
text *in = DatumGetTextP(txt); /* in case it's toasted */
|
||||
List *result = NIL;
|
||||
int len = VARSIZE(in) - VARHDRSZ;
|
||||
char *ptr,
|
||||
*endptr,
|
||||
*workspace,
|
||||
*wsptr = NULL,
|
||||
*startvalue = NULL;
|
||||
typedef enum {
|
||||
CS_WAITKEY,
|
||||
CS_INKEY,
|
||||
CS_INQKEY,
|
||||
CS_WAITEQ,
|
||||
CS_WAITVALUE,
|
||||
CS_INSQVALUE,
|
||||
CS_INDQVALUE,
|
||||
CS_INWVALUE
|
||||
} ds_state;
|
||||
ds_state state = CS_WAITKEY;
|
||||
|
||||
workspace = (char *) palloc(len + 1); /* certainly enough room */
|
||||
ptr = VARDATA(in);
|
||||
endptr = ptr + len;
|
||||
for (; ptr < endptr; ptr++)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case CS_WAITKEY:
|
||||
if (isspace((unsigned char) *ptr) || *ptr == ',')
|
||||
continue;
|
||||
if (*ptr == '"')
|
||||
{
|
||||
wsptr = workspace;
|
||||
state = CS_INQKEY;
|
||||
}
|
||||
else
|
||||
{
|
||||
wsptr = workspace;
|
||||
*wsptr++ = *ptr;
|
||||
state = CS_INKEY;
|
||||
}
|
||||
break;
|
||||
case CS_INKEY:
|
||||
if (isspace((unsigned char) *ptr))
|
||||
{
|
||||
*wsptr++ = '\0';
|
||||
state = CS_WAITEQ;
|
||||
}
|
||||
else if (*ptr == '=')
|
||||
{
|
||||
*wsptr++ = '\0';
|
||||
state = CS_WAITVALUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
*wsptr++ = *ptr;
|
||||
}
|
||||
break;
|
||||
case CS_INQKEY:
|
||||
if (*ptr == '"')
|
||||
{
|
||||
if (ptr+1 < endptr && ptr[1] == '"')
|
||||
{
|
||||
/* copy only one of the two quotes */
|
||||
*wsptr++ = *ptr++;
|
||||
}
|
||||
else
|
||||
{
|
||||
*wsptr++ = '\0';
|
||||
state = CS_WAITEQ;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
*wsptr++ = *ptr;
|
||||
}
|
||||
break;
|
||||
case CS_WAITEQ:
|
||||
if (*ptr == '=')
|
||||
state = CS_WAITVALUE;
|
||||
else if (!isspace((unsigned char) *ptr))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("invalid parameter list format: \"%s\"",
|
||||
TextPGetCString(in))));
|
||||
break;
|
||||
case CS_WAITVALUE:
|
||||
if (*ptr == '\'')
|
||||
{
|
||||
startvalue = wsptr;
|
||||
state = CS_INSQVALUE;
|
||||
}
|
||||
else if (*ptr == 'E' && ptr+1 < endptr && ptr[1] == '\'')
|
||||
{
|
||||
ptr++;
|
||||
startvalue = wsptr;
|
||||
state = CS_INSQVALUE;
|
||||
}
|
||||
else if (*ptr == '"')
|
||||
{
|
||||
startvalue = wsptr;
|
||||
state = CS_INDQVALUE;
|
||||
}
|
||||
else if (!isspace((unsigned char) *ptr))
|
||||
{
|
||||
startvalue = wsptr;
|
||||
*wsptr++ = *ptr;
|
||||
state = CS_INWVALUE;
|
||||
}
|
||||
break;
|
||||
case CS_INSQVALUE:
|
||||
if (*ptr == '\'')
|
||||
{
|
||||
if (ptr+1 < endptr && ptr[1] == '\'')
|
||||
{
|
||||
/* copy only one of the two quotes */
|
||||
*wsptr++ = *ptr++;
|
||||
}
|
||||
else
|
||||
{
|
||||
*wsptr++ = '\0';
|
||||
result = lappend(result,
|
||||
makeDefElem(pstrdup(workspace),
|
||||
(Node *) makeString(pstrdup(startvalue))));
|
||||
state = CS_WAITKEY;
|
||||
}
|
||||
}
|
||||
else if (*ptr == '\\')
|
||||
{
|
||||
if (ptr+1 < endptr && ptr[1] == '\\')
|
||||
{
|
||||
/* copy only one of the two backslashes */
|
||||
*wsptr++ = *ptr++;
|
||||
}
|
||||
else
|
||||
*wsptr++ = *ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
*wsptr++ = *ptr;
|
||||
}
|
||||
break;
|
||||
case CS_INDQVALUE:
|
||||
if (*ptr == '"')
|
||||
{
|
||||
if (ptr+1 < endptr && ptr[1] == '"')
|
||||
{
|
||||
/* copy only one of the two quotes */
|
||||
*wsptr++ = *ptr++;
|
||||
}
|
||||
else
|
||||
{
|
||||
*wsptr++ = '\0';
|
||||
result = lappend(result,
|
||||
makeDefElem(pstrdup(workspace),
|
||||
(Node *) makeString(pstrdup(startvalue))));
|
||||
state = CS_WAITKEY;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
*wsptr++ = *ptr;
|
||||
}
|
||||
break;
|
||||
case CS_INWVALUE:
|
||||
if (*ptr == ',' || isspace((unsigned char) *ptr))
|
||||
{
|
||||
*wsptr++ = '\0';
|
||||
result = lappend(result,
|
||||
makeDefElem(pstrdup(workspace),
|
||||
(Node *) makeString(pstrdup(startvalue))));
|
||||
state = CS_WAITKEY;
|
||||
}
|
||||
else
|
||||
{
|
||||
*wsptr++ = *ptr;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unrecognized deserialize_deflist state: %d",
|
||||
state);
|
||||
}
|
||||
}
|
||||
|
||||
if (state == CS_INWVALUE)
|
||||
{
|
||||
*wsptr++ = '\0';
|
||||
result = lappend(result,
|
||||
makeDefElem(pstrdup(workspace),
|
||||
(Node *) makeString(pstrdup(startvalue))));
|
||||
}
|
||||
else if (state != CS_WAITKEY)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("invalid parameter list format: \"%s\"",
|
||||
TextPGetCString(in))));
|
||||
|
||||
pfree(workspace);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
Reference in New Issue
Block a user