diff options
Diffstat (limited to 'src/backend/commands/tsearchcmds.c')
-rw-r--r-- | src/backend/commands/tsearchcmds.c | 451 |
1 files changed, 393 insertions, 58 deletions
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c index af34c58c7c2..7c5a1c49a33 100644 --- a/src/backend/commands/tsearchcmds.c +++ b/src/backend/commands/tsearchcmds.c @@ -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: @@ -408,6 +411,53 @@ makeDictionaryDependencies(HeapTuple tuple) } /* + * 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 */ void @@ -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) + else { - 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] = ' '; - } + /* Assume it's an option for the dictionary itself */ + dictoptions = lappend(dictoptions, defel); } - else - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("text search dictionary parameter \"%s\" not recognized", - defel->defname))); } /* * 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; +} |