mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-08-21 12:03:30 +02:00
Since collation is effectively an argument, not a property of the function, FmgrInfo is really the wrong place for it; and this becomes critical in cases where a cached FmgrInfo is used for varying purposes that might need different collation settings. Fix by passing it in FunctionCallInfoData instead. In particular this allows a clean fix for bug #5970 (record_cmp not working). This requires touching a bit more code than the original method, but nobody ever thought that collations would not be an invasive patch...
2480 lines
60 KiB
C
2480 lines
60 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* tsearchcmds.c
|
|
*
|
|
* Routines for tsearch manipulation commands
|
|
*
|
|
* Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/commands/tsearchcmds.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "access/heapam.h"
|
|
#include "access/genam.h"
|
|
#include "access/xact.h"
|
|
#include "catalog/dependency.h"
|
|
#include "catalog/indexing.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/objectaccess.h"
|
|
#include "catalog/pg_namespace.h"
|
|
#include "catalog/pg_proc.h"
|
|
#include "catalog/pg_ts_config.h"
|
|
#include "catalog/pg_ts_config_map.h"
|
|
#include "catalog/pg_ts_dict.h"
|
|
#include "catalog/pg_ts_parser.h"
|
|
#include "catalog/pg_ts_template.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "commands/alter.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"
|
|
#include "tsearch/ts_utils.h"
|
|
#include "utils/acl.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/catcache.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/syscache.h"
|
|
#include "utils/tqual.h"
|
|
|
|
|
|
static void MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
|
|
HeapTuple tup, Relation relMap);
|
|
static void DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
|
|
HeapTuple tup, Relation relMap);
|
|
|
|
|
|
/* --------------------- TS Parser commands ------------------------ */
|
|
|
|
/*
|
|
* lookup a parser support function and return its OID (as a Datum)
|
|
*
|
|
* attnum is the pg_ts_parser column the function will go into
|
|
*/
|
|
static Datum
|
|
get_ts_parser_func(DefElem *defel, int attnum)
|
|
{
|
|
List *funcName = defGetQualifiedName(defel);
|
|
Oid typeId[3];
|
|
Oid retTypeId;
|
|
int nargs;
|
|
Oid procOid;
|
|
|
|
retTypeId = INTERNALOID; /* correct for most */
|
|
typeId[0] = INTERNALOID;
|
|
switch (attnum)
|
|
{
|
|
case Anum_pg_ts_parser_prsstart:
|
|
nargs = 2;
|
|
typeId[1] = INT4OID;
|
|
break;
|
|
case Anum_pg_ts_parser_prstoken:
|
|
nargs = 3;
|
|
typeId[1] = INTERNALOID;
|
|
typeId[2] = INTERNALOID;
|
|
break;
|
|
case Anum_pg_ts_parser_prsend:
|
|
nargs = 1;
|
|
retTypeId = VOIDOID;
|
|
break;
|
|
case Anum_pg_ts_parser_prsheadline:
|
|
nargs = 3;
|
|
typeId[1] = INTERNALOID;
|
|
typeId[2] = TSQUERYOID;
|
|
break;
|
|
case Anum_pg_ts_parser_prslextype:
|
|
nargs = 1;
|
|
/*
|
|
* Note: because the lextype method returns type internal, it must
|
|
* have an internal-type argument for security reasons. The
|
|
* argument is not actually used, but is just passed as a zero.
|
|
*/
|
|
break;
|
|
default:
|
|
/* should not be here */
|
|
elog(ERROR, "unrecognized attribute for text search parser: %d",
|
|
attnum);
|
|
nargs = 0; /* keep compiler quiet */
|
|
}
|
|
|
|
procOid = LookupFuncName(funcName, nargs, typeId, false);
|
|
if (get_func_rettype(procOid) != retTypeId)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("function %s should return type %s",
|
|
func_signature_string(funcName, nargs, NIL, typeId),
|
|
format_type_be(retTypeId))));
|
|
|
|
return ObjectIdGetDatum(procOid);
|
|
}
|
|
|
|
/*
|
|
* make pg_depend entries for a new pg_ts_parser entry
|
|
*/
|
|
static void
|
|
makeParserDependencies(HeapTuple tuple)
|
|
{
|
|
Form_pg_ts_parser prs = (Form_pg_ts_parser) GETSTRUCT(tuple);
|
|
ObjectAddress myself,
|
|
referenced;
|
|
|
|
myself.classId = TSParserRelationId;
|
|
myself.objectId = HeapTupleGetOid(tuple);
|
|
myself.objectSubId = 0;
|
|
|
|
/* dependency on namespace */
|
|
referenced.classId = NamespaceRelationId;
|
|
referenced.objectId = prs->prsnamespace;
|
|
referenced.objectSubId = 0;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
/* dependency on extension */
|
|
recordDependencyOnCurrentExtension(&myself);
|
|
|
|
/* dependencies on functions */
|
|
referenced.classId = ProcedureRelationId;
|
|
referenced.objectSubId = 0;
|
|
|
|
referenced.objectId = prs->prsstart;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
referenced.objectId = prs->prstoken;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
referenced.objectId = prs->prsend;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
referenced.objectId = prs->prslextype;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
if (OidIsValid(prs->prsheadline))
|
|
{
|
|
referenced.objectId = prs->prsheadline;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CREATE TEXT SEARCH PARSER
|
|
*/
|
|
void
|
|
DefineTSParser(List *names, List *parameters)
|
|
{
|
|
char *prsname;
|
|
ListCell *pl;
|
|
Relation prsRel;
|
|
HeapTuple tup;
|
|
Datum values[Natts_pg_ts_parser];
|
|
bool nulls[Natts_pg_ts_parser];
|
|
NameData pname;
|
|
Oid prsOid;
|
|
Oid namespaceoid;
|
|
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to create text search parsers")));
|
|
|
|
/* Convert list of names to a name and namespace */
|
|
namespaceoid = QualifiedNameGetCreationNamespace(names, &prsname);
|
|
|
|
/* initialize tuple fields with name/namespace */
|
|
memset(values, 0, sizeof(values));
|
|
memset(nulls, false, sizeof(nulls));
|
|
|
|
namestrcpy(&pname, prsname);
|
|
values[Anum_pg_ts_parser_prsname - 1] = NameGetDatum(&pname);
|
|
values[Anum_pg_ts_parser_prsnamespace - 1] = ObjectIdGetDatum(namespaceoid);
|
|
|
|
/*
|
|
* loop over the definition list and extract the information we need.
|
|
*/
|
|
foreach(pl, parameters)
|
|
{
|
|
DefElem *defel = (DefElem *) lfirst(pl);
|
|
|
|
if (pg_strcasecmp(defel->defname, "start") == 0)
|
|
{
|
|
values[Anum_pg_ts_parser_prsstart - 1] =
|
|
get_ts_parser_func(defel, Anum_pg_ts_parser_prsstart);
|
|
}
|
|
else if (pg_strcasecmp(defel->defname, "gettoken") == 0)
|
|
{
|
|
values[Anum_pg_ts_parser_prstoken - 1] =
|
|
get_ts_parser_func(defel, Anum_pg_ts_parser_prstoken);
|
|
}
|
|
else if (pg_strcasecmp(defel->defname, "end") == 0)
|
|
{
|
|
values[Anum_pg_ts_parser_prsend - 1] =
|
|
get_ts_parser_func(defel, Anum_pg_ts_parser_prsend);
|
|
}
|
|
else if (pg_strcasecmp(defel->defname, "headline") == 0)
|
|
{
|
|
values[Anum_pg_ts_parser_prsheadline - 1] =
|
|
get_ts_parser_func(defel, Anum_pg_ts_parser_prsheadline);
|
|
}
|
|
else if (pg_strcasecmp(defel->defname, "lextypes") == 0)
|
|
{
|
|
values[Anum_pg_ts_parser_prslextype - 1] =
|
|
get_ts_parser_func(defel, Anum_pg_ts_parser_prslextype);
|
|
}
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("text search parser parameter \"%s\" not recognized",
|
|
defel->defname)));
|
|
}
|
|
|
|
/*
|
|
* Validation
|
|
*/
|
|
if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsstart - 1])))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("text search parser start method is required")));
|
|
|
|
if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prstoken - 1])))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("text search parser gettoken method is required")));
|
|
|
|
if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsend - 1])))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("text search parser end method is required")));
|
|
|
|
if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prslextype - 1])))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("text search parser lextypes method is required")));
|
|
|
|
/*
|
|
* Looks good, insert
|
|
*/
|
|
prsRel = heap_open(TSParserRelationId, RowExclusiveLock);
|
|
|
|
tup = heap_form_tuple(prsRel->rd_att, values, nulls);
|
|
|
|
prsOid = simple_heap_insert(prsRel, tup);
|
|
|
|
CatalogUpdateIndexes(prsRel, tup);
|
|
|
|
makeParserDependencies(tup);
|
|
|
|
/* Post creation hook for new text search parser */
|
|
InvokeObjectAccessHook(OAT_POST_CREATE, TSParserRelationId, prsOid, 0);
|
|
|
|
heap_freetuple(tup);
|
|
|
|
heap_close(prsRel, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* DROP TEXT SEARCH PARSER
|
|
*/
|
|
void
|
|
RemoveTSParsers(DropStmt *drop)
|
|
{
|
|
ObjectAddresses *objects;
|
|
ListCell *cell;
|
|
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to drop text search parsers")));
|
|
|
|
/*
|
|
* First we identify all the objects, then we delete them in a single
|
|
* performMultipleDeletions() call. This is to avoid unwanted DROP
|
|
* RESTRICT errors if one of the objects depends on another.
|
|
*/
|
|
objects = new_object_addresses();
|
|
|
|
foreach(cell, drop->objects)
|
|
{
|
|
List *names = (List *) lfirst(cell);
|
|
Oid prsOid;
|
|
ObjectAddress object;
|
|
|
|
prsOid = get_ts_parser_oid(names, true);
|
|
|
|
if (!OidIsValid(prsOid))
|
|
{
|
|
if (!drop->missing_ok)
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("text search parser \"%s\" does not exist",
|
|
NameListToString(names))));
|
|
}
|
|
else
|
|
{
|
|
ereport(NOTICE,
|
|
(errmsg("text search parser \"%s\" does not exist, skipping",
|
|
NameListToString(names))));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
object.classId = TSParserRelationId;
|
|
object.objectId = prsOid;
|
|
object.objectSubId = 0;
|
|
|
|
add_exact_object_address(&object, objects);
|
|
}
|
|
|
|
performMultipleDeletions(objects, drop->behavior);
|
|
|
|
free_object_addresses(objects);
|
|
}
|
|
|
|
/*
|
|
* Guts of TS parser deletion.
|
|
*/
|
|
void
|
|
RemoveTSParserById(Oid prsId)
|
|
{
|
|
Relation relation;
|
|
HeapTuple tup;
|
|
|
|
relation = heap_open(TSParserRelationId, RowExclusiveLock);
|
|
|
|
tup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId));
|
|
|
|
if (!HeapTupleIsValid(tup))
|
|
elog(ERROR, "cache lookup failed for text search parser %u", prsId);
|
|
|
|
simple_heap_delete(relation, &tup->t_self);
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
heap_close(relation, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH PARSER RENAME
|
|
*/
|
|
void
|
|
RenameTSParser(List *oldname, const char *newname)
|
|
{
|
|
HeapTuple tup;
|
|
Relation rel;
|
|
Oid prsId;
|
|
Oid namespaceOid;
|
|
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to rename text search parsers")));
|
|
|
|
rel = heap_open(TSParserRelationId, RowExclusiveLock);
|
|
|
|
prsId = get_ts_parser_oid(oldname, false);
|
|
|
|
tup = SearchSysCacheCopy1(TSPARSEROID, ObjectIdGetDatum(prsId));
|
|
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for text search parser %u", prsId);
|
|
|
|
namespaceOid = ((Form_pg_ts_parser) GETSTRUCT(tup))->prsnamespace;
|
|
|
|
if (SearchSysCacheExists2(TSPARSERNAMENSP,
|
|
PointerGetDatum(newname),
|
|
ObjectIdGetDatum(namespaceOid)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("text search parser \"%s\" already exists",
|
|
newname)));
|
|
|
|
namestrcpy(&(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname), newname);
|
|
simple_heap_update(rel, &tup->t_self, tup);
|
|
CatalogUpdateIndexes(rel, tup);
|
|
|
|
heap_close(rel, NoLock);
|
|
heap_freetuple(tup);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH PARSER any_name SET SCHEMA name
|
|
*/
|
|
void
|
|
AlterTSParserNamespace(List *name, const char *newschema)
|
|
{
|
|
Oid prsId,
|
|
nspOid;
|
|
Relation rel;
|
|
|
|
rel = heap_open(TSParserRelationId, RowExclusiveLock);
|
|
|
|
prsId = get_ts_parser_oid(name, false);
|
|
|
|
/* get schema OID */
|
|
nspOid = LookupCreationNamespace(newschema);
|
|
|
|
AlterObjectNamespace(rel, TSPARSEROID, TSPARSERNAMENSP,
|
|
prsId, nspOid,
|
|
Anum_pg_ts_parser_prsname,
|
|
Anum_pg_ts_parser_prsnamespace,
|
|
-1, -1);
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
}
|
|
|
|
Oid
|
|
AlterTSParserNamespace_oid(Oid prsId, Oid newNspOid)
|
|
{
|
|
Oid oldNspOid;
|
|
Relation rel;
|
|
|
|
rel = heap_open(TSParserRelationId, RowExclusiveLock);
|
|
|
|
oldNspOid =
|
|
AlterObjectNamespace(rel, TSPARSEROID, TSPARSERNAMENSP,
|
|
prsId, newNspOid,
|
|
Anum_pg_ts_parser_prsname,
|
|
Anum_pg_ts_parser_prsnamespace,
|
|
-1, -1);
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
|
|
return oldNspOid;
|
|
}
|
|
|
|
/* ---------------------- TS Dictionary commands -----------------------*/
|
|
|
|
/*
|
|
* make pg_depend entries for a new pg_ts_dict entry
|
|
*/
|
|
static void
|
|
makeDictionaryDependencies(HeapTuple tuple)
|
|
{
|
|
Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tuple);
|
|
ObjectAddress myself,
|
|
referenced;
|
|
|
|
myself.classId = TSDictionaryRelationId;
|
|
myself.objectId = HeapTupleGetOid(tuple);
|
|
myself.objectSubId = 0;
|
|
|
|
/* dependency on namespace */
|
|
referenced.classId = NamespaceRelationId;
|
|
referenced.objectId = dict->dictnamespace;
|
|
referenced.objectSubId = 0;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
/* dependency on owner */
|
|
recordDependencyOnOwner(myself.classId, myself.objectId, dict->dictowner);
|
|
|
|
/* dependency on extension */
|
|
recordDependencyOnCurrentExtension(&myself);
|
|
|
|
/* dependency on template */
|
|
referenced.classId = TSTemplateRelationId;
|
|
referenced.objectId = dict->dicttemplate;
|
|
referenced.objectSubId = 0;
|
|
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;
|
|
|
|
/*
|
|
* Suppress this test when running in a standalone backend. This is a
|
|
* hack to allow initdb to create prefab dictionaries that might not
|
|
* actually be usable in template1's encoding (due to using external files
|
|
* that can't be translated into template1's encoding). We want to create
|
|
* them anyway, since they might be usable later in other databases.
|
|
*/
|
|
if (!IsUnderPostmaster)
|
|
return;
|
|
|
|
tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId));
|
|
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
|
|
DefineTSDictionary(List *names, List *parameters)
|
|
{
|
|
ListCell *pl;
|
|
Relation dictRel;
|
|
HeapTuple tup;
|
|
Datum values[Natts_pg_ts_dict];
|
|
bool nulls[Natts_pg_ts_dict];
|
|
NameData dname;
|
|
Oid templId = InvalidOid;
|
|
List *dictoptions = NIL;
|
|
Oid dictOid;
|
|
Oid namespaceoid;
|
|
AclResult aclresult;
|
|
char *dictname;
|
|
|
|
/* Convert list of names to a name and namespace */
|
|
namespaceoid = QualifiedNameGetCreationNamespace(names, &dictname);
|
|
|
|
/* Check we have creation rights in target namespace */
|
|
aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
|
|
get_namespace_name(namespaceoid));
|
|
|
|
/*
|
|
* loop over the definition list and extract the information we need.
|
|
*/
|
|
foreach(pl, parameters)
|
|
{
|
|
DefElem *defel = (DefElem *) lfirst(pl);
|
|
|
|
if (pg_strcasecmp(defel->defname, "template") == 0)
|
|
{
|
|
templId = get_ts_template_oid(defGetQualifiedName(defel), false);
|
|
}
|
|
else
|
|
{
|
|
/* Assume it's an option for the dictionary itself */
|
|
dictoptions = lappend(dictoptions, defel);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Validation
|
|
*/
|
|
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, false, 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] = true;
|
|
|
|
dictRel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
|
|
|
|
tup = heap_form_tuple(dictRel->rd_att, values, nulls);
|
|
|
|
dictOid = simple_heap_insert(dictRel, tup);
|
|
|
|
CatalogUpdateIndexes(dictRel, tup);
|
|
|
|
makeDictionaryDependencies(tup);
|
|
|
|
/* Post creation hook for new text search dictionary */
|
|
InvokeObjectAccessHook(OAT_POST_CREATE,
|
|
TSDictionaryRelationId, dictOid, 0);
|
|
|
|
heap_freetuple(tup);
|
|
|
|
heap_close(dictRel, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH DICTIONARY RENAME
|
|
*/
|
|
void
|
|
RenameTSDictionary(List *oldname, const char *newname)
|
|
{
|
|
HeapTuple tup;
|
|
Relation rel;
|
|
Oid dictId;
|
|
Oid namespaceOid;
|
|
AclResult aclresult;
|
|
|
|
rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
|
|
|
|
dictId = get_ts_dict_oid(oldname, false);
|
|
|
|
tup = SearchSysCacheCopy1(TSDICTOID, ObjectIdGetDatum(dictId));
|
|
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for text search dictionary %u",
|
|
dictId);
|
|
|
|
namespaceOid = ((Form_pg_ts_dict) GETSTRUCT(tup))->dictnamespace;
|
|
|
|
if (SearchSysCacheExists2(TSDICTNAMENSP,
|
|
PointerGetDatum(newname),
|
|
ObjectIdGetDatum(namespaceOid)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("text search dictionary \"%s\" already exists",
|
|
newname)));
|
|
|
|
/* must be owner */
|
|
if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
|
|
NameListToString(oldname));
|
|
|
|
/* must have CREATE privilege on namespace */
|
|
aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
|
|
get_namespace_name(namespaceOid));
|
|
|
|
namestrcpy(&(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname), newname);
|
|
simple_heap_update(rel, &tup->t_self, tup);
|
|
CatalogUpdateIndexes(rel, tup);
|
|
|
|
heap_close(rel, NoLock);
|
|
heap_freetuple(tup);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH DICTIONARY any_name SET SCHEMA name
|
|
*/
|
|
void
|
|
AlterTSDictionaryNamespace(List *name, const char *newschema)
|
|
{
|
|
Oid dictId,
|
|
nspOid;
|
|
Relation rel;
|
|
|
|
rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
|
|
|
|
dictId = get_ts_dict_oid(name, false);
|
|
|
|
/* get schema OID */
|
|
nspOid = LookupCreationNamespace(newschema);
|
|
|
|
AlterObjectNamespace(rel, TSDICTOID, TSDICTNAMENSP,
|
|
dictId, nspOid,
|
|
Anum_pg_ts_dict_dictname,
|
|
Anum_pg_ts_dict_dictnamespace,
|
|
Anum_pg_ts_dict_dictowner,
|
|
ACL_KIND_TSDICTIONARY);
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
}
|
|
|
|
Oid
|
|
AlterTSDictionaryNamespace_oid(Oid dictId, Oid newNspOid)
|
|
{
|
|
Oid oldNspOid;
|
|
Relation rel;
|
|
|
|
rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
|
|
|
|
oldNspOid =
|
|
AlterObjectNamespace(rel, TSDICTOID, TSDICTNAMENSP,
|
|
dictId, newNspOid,
|
|
Anum_pg_ts_dict_dictname,
|
|
Anum_pg_ts_dict_dictnamespace,
|
|
Anum_pg_ts_dict_dictowner,
|
|
ACL_KIND_TSDICTIONARY);
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
|
|
return oldNspOid;
|
|
}
|
|
|
|
/*
|
|
* DROP TEXT SEARCH DICTIONARY
|
|
*/
|
|
void
|
|
RemoveTSDictionaries(DropStmt *drop)
|
|
{
|
|
ObjectAddresses *objects;
|
|
ListCell *cell;
|
|
|
|
/*
|
|
* First we identify all the objects, then we delete them in a single
|
|
* performMultipleDeletions() call. This is to avoid unwanted DROP
|
|
* RESTRICT errors if one of the objects depends on another.
|
|
*/
|
|
objects = new_object_addresses();
|
|
|
|
foreach(cell, drop->objects)
|
|
{
|
|
List *names = (List *) lfirst(cell);
|
|
Oid dictOid;
|
|
ObjectAddress object;
|
|
HeapTuple tup;
|
|
Oid namespaceId;
|
|
|
|
dictOid = get_ts_dict_oid(names, true);
|
|
|
|
if (!OidIsValid(dictOid))
|
|
{
|
|
if (!drop->missing_ok)
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("text search dictionary \"%s\" does not exist",
|
|
NameListToString(names))));
|
|
}
|
|
else
|
|
{
|
|
ereport(NOTICE,
|
|
(errmsg("text search dictionary \"%s\" does not exist, skipping",
|
|
NameListToString(names))));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictOid));
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for text search dictionary %u",
|
|
dictOid);
|
|
|
|
/* Permission check: must own dictionary or its namespace */
|
|
namespaceId = ((Form_pg_ts_dict) GETSTRUCT(tup))->dictnamespace;
|
|
if (!pg_ts_dict_ownercheck(dictOid, GetUserId()) &&
|
|
!pg_namespace_ownercheck(namespaceId, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
|
|
NameListToString(names));
|
|
|
|
object.classId = TSDictionaryRelationId;
|
|
object.objectId = dictOid;
|
|
object.objectSubId = 0;
|
|
|
|
add_exact_object_address(&object, objects);
|
|
|
|
ReleaseSysCache(tup);
|
|
}
|
|
|
|
performMultipleDeletions(objects, drop->behavior);
|
|
|
|
free_object_addresses(objects);
|
|
}
|
|
|
|
/*
|
|
* Guts of TS dictionary deletion.
|
|
*/
|
|
void
|
|
RemoveTSDictionaryById(Oid dictId)
|
|
{
|
|
Relation relation;
|
|
HeapTuple tup;
|
|
|
|
relation = heap_open(TSDictionaryRelationId, RowExclusiveLock);
|
|
|
|
tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
|
|
|
|
if (!HeapTupleIsValid(tup))
|
|
elog(ERROR, "cache lookup failed for text search dictionary %u",
|
|
dictId);
|
|
|
|
simple_heap_delete(relation, &tup->t_self);
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
heap_close(relation, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH DICTIONARY
|
|
*/
|
|
void
|
|
AlterTSDictionary(AlterTSDictionaryStmt *stmt)
|
|
{
|
|
HeapTuple tup,
|
|
newtup;
|
|
Relation rel;
|
|
Oid dictId;
|
|
ListCell *pl;
|
|
List *dictoptions;
|
|
Datum opt;
|
|
bool isnull;
|
|
Datum repl_val[Natts_pg_ts_dict];
|
|
bool repl_null[Natts_pg_ts_dict];
|
|
bool repl_repl[Natts_pg_ts_dict];
|
|
|
|
dictId = get_ts_dict_oid(stmt->dictname, false);
|
|
|
|
rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
|
|
|
|
tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
|
|
|
|
if (!HeapTupleIsValid(tup))
|
|
elog(ERROR, "cache lookup failed for text search dictionary %u",
|
|
dictId);
|
|
|
|
/* must be owner */
|
|
if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
|
|
NameListToString(stmt->dictname));
|
|
|
|
/* 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);
|
|
|
|
/*
|
|
* Modify the options list as per specified changes
|
|
*/
|
|
foreach(pl, stmt->options)
|
|
{
|
|
DefElem *defel = (DefElem *) lfirst(pl);
|
|
ListCell *cell;
|
|
ListCell *prev;
|
|
ListCell *next;
|
|
|
|
/*
|
|
* Remove any matches ...
|
|
*/
|
|
prev = NULL;
|
|
for (cell = list_head(dictoptions); cell; cell = next)
|
|
{
|
|
DefElem *oldel = (DefElem *) lfirst(cell);
|
|
|
|
next = lnext(cell);
|
|
if (pg_strcasecmp(oldel->defname, defel->defname) == 0)
|
|
dictoptions = list_delete_cell(dictoptions, cell, prev);
|
|
else
|
|
prev = cell;
|
|
}
|
|
|
|
/*
|
|
* 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, false, sizeof(repl_null));
|
|
memset(repl_repl, false, 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] = true;
|
|
repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = true;
|
|
|
|
newtup = heap_modify_tuple(tup, RelationGetDescr(rel),
|
|
repl_val, repl_null, repl_repl);
|
|
|
|
simple_heap_update(rel, &newtup->t_self, newtup);
|
|
|
|
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);
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH DICTIONARY OWNER
|
|
*/
|
|
void
|
|
AlterTSDictionaryOwner(List *name, Oid newOwnerId)
|
|
{
|
|
HeapTuple tup;
|
|
Relation rel;
|
|
Oid dictId;
|
|
Oid namespaceOid;
|
|
AclResult aclresult;
|
|
Form_pg_ts_dict form;
|
|
|
|
rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
|
|
|
|
dictId = get_ts_dict_oid(name, false);
|
|
|
|
tup = SearchSysCacheCopy1(TSDICTOID, ObjectIdGetDatum(dictId));
|
|
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for text search dictionary %u",
|
|
dictId);
|
|
|
|
form = (Form_pg_ts_dict) GETSTRUCT(tup);
|
|
namespaceOid = form->dictnamespace;
|
|
|
|
if (form->dictowner != newOwnerId)
|
|
{
|
|
/* Superusers can always do it */
|
|
if (!superuser())
|
|
{
|
|
/* must be owner */
|
|
if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
|
|
NameListToString(name));
|
|
|
|
/* Must be able to become new owner */
|
|
check_is_member_of_role(GetUserId(), newOwnerId);
|
|
|
|
/* New owner must have CREATE privilege on namespace */
|
|
aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, ACL_CREATE);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
|
|
get_namespace_name(namespaceOid));
|
|
}
|
|
|
|
form->dictowner = newOwnerId;
|
|
|
|
simple_heap_update(rel, &tup->t_self, tup);
|
|
CatalogUpdateIndexes(rel, tup);
|
|
|
|
/* Update owner dependency reference */
|
|
changeDependencyOnOwner(TSDictionaryRelationId, HeapTupleGetOid(tup),
|
|
newOwnerId);
|
|
}
|
|
|
|
heap_close(rel, NoLock);
|
|
heap_freetuple(tup);
|
|
}
|
|
|
|
/* ---------------------- TS Template commands -----------------------*/
|
|
|
|
/*
|
|
* lookup a template support function and return its OID (as a Datum)
|
|
*
|
|
* attnum is the pg_ts_template column the function will go into
|
|
*/
|
|
static Datum
|
|
get_ts_template_func(DefElem *defel, int attnum)
|
|
{
|
|
List *funcName = defGetQualifiedName(defel);
|
|
Oid typeId[4];
|
|
Oid retTypeId;
|
|
int nargs;
|
|
Oid procOid;
|
|
|
|
retTypeId = INTERNALOID;
|
|
typeId[0] = INTERNALOID;
|
|
typeId[1] = INTERNALOID;
|
|
typeId[2] = INTERNALOID;
|
|
typeId[3] = INTERNALOID;
|
|
switch (attnum)
|
|
{
|
|
case Anum_pg_ts_template_tmplinit:
|
|
nargs = 1;
|
|
break;
|
|
case Anum_pg_ts_template_tmpllexize:
|
|
nargs = 4;
|
|
break;
|
|
default:
|
|
/* should not be here */
|
|
elog(ERROR, "unrecognized attribute for text search template: %d",
|
|
attnum);
|
|
nargs = 0; /* keep compiler quiet */
|
|
}
|
|
|
|
procOid = LookupFuncName(funcName, nargs, typeId, false);
|
|
if (get_func_rettype(procOid) != retTypeId)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("function %s should return type %s",
|
|
func_signature_string(funcName, nargs, NIL, typeId),
|
|
format_type_be(retTypeId))));
|
|
|
|
return ObjectIdGetDatum(procOid);
|
|
}
|
|
|
|
/*
|
|
* make pg_depend entries for a new pg_ts_template entry
|
|
*/
|
|
static void
|
|
makeTSTemplateDependencies(HeapTuple tuple)
|
|
{
|
|
Form_pg_ts_template tmpl = (Form_pg_ts_template) GETSTRUCT(tuple);
|
|
ObjectAddress myself,
|
|
referenced;
|
|
|
|
myself.classId = TSTemplateRelationId;
|
|
myself.objectId = HeapTupleGetOid(tuple);
|
|
myself.objectSubId = 0;
|
|
|
|
/* dependency on namespace */
|
|
referenced.classId = NamespaceRelationId;
|
|
referenced.objectId = tmpl->tmplnamespace;
|
|
referenced.objectSubId = 0;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
/* dependency on extension */
|
|
recordDependencyOnCurrentExtension(&myself);
|
|
|
|
/* dependencies on functions */
|
|
referenced.classId = ProcedureRelationId;
|
|
referenced.objectSubId = 0;
|
|
|
|
referenced.objectId = tmpl->tmpllexize;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
if (OidIsValid(tmpl->tmplinit))
|
|
{
|
|
referenced.objectId = tmpl->tmplinit;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CREATE TEXT SEARCH TEMPLATE
|
|
*/
|
|
void
|
|
DefineTSTemplate(List *names, List *parameters)
|
|
{
|
|
ListCell *pl;
|
|
Relation tmplRel;
|
|
HeapTuple tup;
|
|
Datum values[Natts_pg_ts_template];
|
|
bool nulls[Natts_pg_ts_template];
|
|
NameData dname;
|
|
int i;
|
|
Oid dictOid;
|
|
Oid namespaceoid;
|
|
char *tmplname;
|
|
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to create text search templates")));
|
|
|
|
/* Convert list of names to a name and namespace */
|
|
namespaceoid = QualifiedNameGetCreationNamespace(names, &tmplname);
|
|
|
|
for (i = 0; i < Natts_pg_ts_template; i++)
|
|
{
|
|
nulls[i] = false;
|
|
values[i] = ObjectIdGetDatum(InvalidOid);
|
|
}
|
|
|
|
namestrcpy(&dname, tmplname);
|
|
values[Anum_pg_ts_template_tmplname - 1] = NameGetDatum(&dname);
|
|
values[Anum_pg_ts_template_tmplnamespace - 1] = ObjectIdGetDatum(namespaceoid);
|
|
|
|
/*
|
|
* loop over the definition list and extract the information we need.
|
|
*/
|
|
foreach(pl, parameters)
|
|
{
|
|
DefElem *defel = (DefElem *) lfirst(pl);
|
|
|
|
if (pg_strcasecmp(defel->defname, "init") == 0)
|
|
{
|
|
values[Anum_pg_ts_template_tmplinit - 1] =
|
|
get_ts_template_func(defel, Anum_pg_ts_template_tmplinit);
|
|
nulls[Anum_pg_ts_template_tmplinit - 1] = false;
|
|
}
|
|
else if (pg_strcasecmp(defel->defname, "lexize") == 0)
|
|
{
|
|
values[Anum_pg_ts_template_tmpllexize - 1] =
|
|
get_ts_template_func(defel, Anum_pg_ts_template_tmpllexize);
|
|
nulls[Anum_pg_ts_template_tmpllexize - 1] = false;
|
|
}
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("text search template parameter \"%s\" not recognized",
|
|
defel->defname)));
|
|
}
|
|
|
|
/*
|
|
* Validation
|
|
*/
|
|
if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_template_tmpllexize - 1])))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("text search template lexize method is required")));
|
|
|
|
/*
|
|
* Looks good, insert
|
|
*/
|
|
|
|
tmplRel = heap_open(TSTemplateRelationId, RowExclusiveLock);
|
|
|
|
tup = heap_form_tuple(tmplRel->rd_att, values, nulls);
|
|
|
|
dictOid = simple_heap_insert(tmplRel, tup);
|
|
|
|
CatalogUpdateIndexes(tmplRel, tup);
|
|
|
|
makeTSTemplateDependencies(tup);
|
|
|
|
/* Post creation hook for new text search template */
|
|
InvokeObjectAccessHook(OAT_POST_CREATE, TSTemplateRelationId, dictOid, 0);
|
|
|
|
heap_freetuple(tup);
|
|
|
|
heap_close(tmplRel, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH TEMPLATE RENAME
|
|
*/
|
|
void
|
|
RenameTSTemplate(List *oldname, const char *newname)
|
|
{
|
|
HeapTuple tup;
|
|
Relation rel;
|
|
Oid tmplId;
|
|
Oid namespaceOid;
|
|
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to rename text search templates")));
|
|
|
|
rel = heap_open(TSTemplateRelationId, RowExclusiveLock);
|
|
|
|
tmplId = get_ts_template_oid(oldname, false);
|
|
|
|
tup = SearchSysCacheCopy1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId));
|
|
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for text search template %u",
|
|
tmplId);
|
|
|
|
namespaceOid = ((Form_pg_ts_template) GETSTRUCT(tup))->tmplnamespace;
|
|
|
|
if (SearchSysCacheExists2(TSTEMPLATENAMENSP,
|
|
PointerGetDatum(newname),
|
|
ObjectIdGetDatum(namespaceOid)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("text search template \"%s\" already exists",
|
|
newname)));
|
|
|
|
namestrcpy(&(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname), newname);
|
|
simple_heap_update(rel, &tup->t_self, tup);
|
|
CatalogUpdateIndexes(rel, tup);
|
|
|
|
heap_close(rel, NoLock);
|
|
heap_freetuple(tup);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH TEMPLATE any_name SET SCHEMA name
|
|
*/
|
|
void
|
|
AlterTSTemplateNamespace(List *name, const char *newschema)
|
|
{
|
|
Oid tmplId,
|
|
nspOid;
|
|
Relation rel;
|
|
|
|
rel = heap_open(TSTemplateRelationId, RowExclusiveLock);
|
|
|
|
tmplId = get_ts_template_oid(name, false);
|
|
|
|
/* get schema OID */
|
|
nspOid = LookupCreationNamespace(newschema);
|
|
|
|
AlterObjectNamespace(rel, TSTEMPLATEOID, TSTEMPLATENAMENSP,
|
|
tmplId, nspOid,
|
|
Anum_pg_ts_template_tmplname,
|
|
Anum_pg_ts_template_tmplnamespace,
|
|
-1, -1);
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
}
|
|
|
|
Oid
|
|
AlterTSTemplateNamespace_oid(Oid tmplId, Oid newNspOid)
|
|
{
|
|
Oid oldNspOid;
|
|
Relation rel;
|
|
|
|
rel = heap_open(TSTemplateRelationId, RowExclusiveLock);
|
|
|
|
oldNspOid =
|
|
AlterObjectNamespace(rel, TSTEMPLATEOID, TSTEMPLATENAMENSP,
|
|
tmplId, newNspOid,
|
|
Anum_pg_ts_template_tmplname,
|
|
Anum_pg_ts_template_tmplnamespace,
|
|
-1, -1);
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
|
|
return oldNspOid;
|
|
}
|
|
|
|
/*
|
|
* DROP TEXT SEARCH TEMPLATE
|
|
*/
|
|
void
|
|
RemoveTSTemplates(DropStmt *drop)
|
|
{
|
|
ObjectAddresses *objects;
|
|
ListCell *cell;
|
|
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to drop text search templates")));
|
|
|
|
/*
|
|
* First we identify all the objects, then we delete them in a single
|
|
* performMultipleDeletions() call. This is to avoid unwanted DROP
|
|
* RESTRICT errors if one of the objects depends on another.
|
|
*/
|
|
objects = new_object_addresses();
|
|
|
|
foreach(cell, drop->objects)
|
|
{
|
|
List *names = (List *) lfirst(cell);
|
|
Oid tmplOid;
|
|
ObjectAddress object;
|
|
|
|
tmplOid = get_ts_template_oid(names, true);
|
|
|
|
if (!OidIsValid(tmplOid))
|
|
{
|
|
if (!drop->missing_ok)
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("text search template \"%s\" does not exist",
|
|
NameListToString(names))));
|
|
}
|
|
else
|
|
{
|
|
ereport(NOTICE,
|
|
(errmsg("text search template \"%s\" does not exist, skipping",
|
|
NameListToString(names))));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
object.classId = TSTemplateRelationId;
|
|
object.objectId = tmplOid;
|
|
object.objectSubId = 0;
|
|
|
|
add_exact_object_address(&object, objects);
|
|
}
|
|
|
|
performMultipleDeletions(objects, drop->behavior);
|
|
|
|
free_object_addresses(objects);
|
|
}
|
|
|
|
/*
|
|
* Guts of TS template deletion.
|
|
*/
|
|
void
|
|
RemoveTSTemplateById(Oid tmplId)
|
|
{
|
|
Relation relation;
|
|
HeapTuple tup;
|
|
|
|
relation = heap_open(TSTemplateRelationId, RowExclusiveLock);
|
|
|
|
tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId));
|
|
|
|
if (!HeapTupleIsValid(tup))
|
|
elog(ERROR, "cache lookup failed for text search template %u",
|
|
tmplId);
|
|
|
|
simple_heap_delete(relation, &tup->t_self);
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
heap_close(relation, RowExclusiveLock);
|
|
}
|
|
|
|
/* ---------------------- TS Configuration commands -----------------------*/
|
|
|
|
/*
|
|
* Finds syscache tuple of configuration.
|
|
* Returns NULL if no such cfg.
|
|
*/
|
|
static HeapTuple
|
|
GetTSConfigTuple(List *names)
|
|
{
|
|
HeapTuple tup;
|
|
Oid cfgId;
|
|
|
|
cfgId = get_ts_config_oid(names, true);
|
|
if (!OidIsValid(cfgId))
|
|
return NULL;
|
|
|
|
tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
|
|
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for text search configuration %u",
|
|
cfgId);
|
|
|
|
return tup;
|
|
}
|
|
|
|
/*
|
|
* make pg_depend entries for a new or updated pg_ts_config entry
|
|
*
|
|
* Pass opened pg_ts_config_map relation if there might be any config map
|
|
* entries for the config.
|
|
*/
|
|
static void
|
|
makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
|
|
Relation mapRel)
|
|
{
|
|
Form_pg_ts_config cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
|
|
ObjectAddresses *addrs;
|
|
ObjectAddress myself,
|
|
referenced;
|
|
|
|
myself.classId = TSConfigRelationId;
|
|
myself.objectId = HeapTupleGetOid(tuple);
|
|
myself.objectSubId = 0;
|
|
|
|
/* for ALTER case, first flush old dependencies, except extension deps */
|
|
if (removeOld)
|
|
{
|
|
deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
|
|
deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0);
|
|
}
|
|
|
|
/*
|
|
* We use an ObjectAddresses list to remove possible duplicate
|
|
* dependencies from the config map info. The pg_ts_config items
|
|
* shouldn't be duplicates, but might as well fold them all into one call.
|
|
*/
|
|
addrs = new_object_addresses();
|
|
|
|
/* dependency on namespace */
|
|
referenced.classId = NamespaceRelationId;
|
|
referenced.objectId = cfg->cfgnamespace;
|
|
referenced.objectSubId = 0;
|
|
add_exact_object_address(&referenced, addrs);
|
|
|
|
/* dependency on owner */
|
|
recordDependencyOnOwner(myself.classId, myself.objectId, cfg->cfgowner);
|
|
|
|
/* dependency on extension */
|
|
if (!removeOld)
|
|
recordDependencyOnCurrentExtension(&myself);
|
|
|
|
/* dependency on parser */
|
|
referenced.classId = TSParserRelationId;
|
|
referenced.objectId = cfg->cfgparser;
|
|
referenced.objectSubId = 0;
|
|
add_exact_object_address(&referenced, addrs);
|
|
|
|
/* dependencies on dictionaries listed in config map */
|
|
if (mapRel)
|
|
{
|
|
ScanKeyData skey;
|
|
SysScanDesc scan;
|
|
HeapTuple maptup;
|
|
|
|
/* CCI to ensure we can see effects of caller's changes */
|
|
CommandCounterIncrement();
|
|
|
|
ScanKeyInit(&skey,
|
|
Anum_pg_ts_config_map_mapcfg,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(myself.objectId));
|
|
|
|
scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
|
|
SnapshotNow, 1, &skey);
|
|
|
|
while (HeapTupleIsValid((maptup = systable_getnext(scan))))
|
|
{
|
|
Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
|
|
|
|
referenced.classId = TSDictionaryRelationId;
|
|
referenced.objectId = cfgmap->mapdict;
|
|
referenced.objectSubId = 0;
|
|
add_exact_object_address(&referenced, addrs);
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
}
|
|
|
|
/* Record 'em (this includes duplicate elimination) */
|
|
record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
|
|
|
|
free_object_addresses(addrs);
|
|
}
|
|
|
|
/*
|
|
* CREATE TEXT SEARCH CONFIGURATION
|
|
*/
|
|
void
|
|
DefineTSConfiguration(List *names, List *parameters)
|
|
{
|
|
Relation cfgRel;
|
|
Relation mapRel = NULL;
|
|
HeapTuple tup;
|
|
Datum values[Natts_pg_ts_config];
|
|
bool nulls[Natts_pg_ts_config];
|
|
AclResult aclresult;
|
|
Oid namespaceoid;
|
|
char *cfgname;
|
|
NameData cname;
|
|
Oid sourceOid = InvalidOid;
|
|
Oid prsOid = InvalidOid;
|
|
Oid cfgOid;
|
|
ListCell *pl;
|
|
|
|
/* Convert list of names to a name and namespace */
|
|
namespaceoid = QualifiedNameGetCreationNamespace(names, &cfgname);
|
|
|
|
/* Check we have creation rights in target namespace */
|
|
aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
|
|
get_namespace_name(namespaceoid));
|
|
|
|
/*
|
|
* loop over the definition list and extract the information we need.
|
|
*/
|
|
foreach(pl, parameters)
|
|
{
|
|
DefElem *defel = (DefElem *) lfirst(pl);
|
|
|
|
if (pg_strcasecmp(defel->defname, "parser") == 0)
|
|
prsOid = get_ts_parser_oid(defGetQualifiedName(defel), false);
|
|
else if (pg_strcasecmp(defel->defname, "copy") == 0)
|
|
sourceOid = get_ts_config_oid(defGetQualifiedName(defel), false);
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("text search configuration parameter \"%s\" not recognized",
|
|
defel->defname)));
|
|
}
|
|
|
|
if (OidIsValid(sourceOid) && OidIsValid(prsOid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("cannot specify both PARSER and COPY options")));
|
|
|
|
/*
|
|
* Look up source config if given.
|
|
*/
|
|
if (OidIsValid(sourceOid))
|
|
{
|
|
Form_pg_ts_config cfg;
|
|
|
|
tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(sourceOid));
|
|
if (!HeapTupleIsValid(tup))
|
|
elog(ERROR, "cache lookup failed for text search configuration %u",
|
|
sourceOid);
|
|
|
|
cfg = (Form_pg_ts_config) GETSTRUCT(tup);
|
|
|
|
/* use source's parser */
|
|
prsOid = cfg->cfgparser;
|
|
|
|
ReleaseSysCache(tup);
|
|
}
|
|
|
|
/*
|
|
* Validation
|
|
*/
|
|
if (!OidIsValid(prsOid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("text search parser is required")));
|
|
|
|
/*
|
|
* Looks good, build tuple and insert
|
|
*/
|
|
memset(values, 0, sizeof(values));
|
|
memset(nulls, false, sizeof(nulls));
|
|
|
|
namestrcpy(&cname, cfgname);
|
|
values[Anum_pg_ts_config_cfgname - 1] = NameGetDatum(&cname);
|
|
values[Anum_pg_ts_config_cfgnamespace - 1] = ObjectIdGetDatum(namespaceoid);
|
|
values[Anum_pg_ts_config_cfgowner - 1] = ObjectIdGetDatum(GetUserId());
|
|
values[Anum_pg_ts_config_cfgparser - 1] = ObjectIdGetDatum(prsOid);
|
|
|
|
cfgRel = heap_open(TSConfigRelationId, RowExclusiveLock);
|
|
|
|
tup = heap_form_tuple(cfgRel->rd_att, values, nulls);
|
|
|
|
cfgOid = simple_heap_insert(cfgRel, tup);
|
|
|
|
CatalogUpdateIndexes(cfgRel, tup);
|
|
|
|
if (OidIsValid(sourceOid))
|
|
{
|
|
/*
|
|
* Copy token-dicts map from source config
|
|
*/
|
|
ScanKeyData skey;
|
|
SysScanDesc scan;
|
|
HeapTuple maptup;
|
|
|
|
mapRel = heap_open(TSConfigMapRelationId, RowExclusiveLock);
|
|
|
|
ScanKeyInit(&skey,
|
|
Anum_pg_ts_config_map_mapcfg,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(sourceOid));
|
|
|
|
scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
|
|
SnapshotNow, 1, &skey);
|
|
|
|
while (HeapTupleIsValid((maptup = systable_getnext(scan))))
|
|
{
|
|
Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
|
|
HeapTuple newmaptup;
|
|
Datum mapvalues[Natts_pg_ts_config_map];
|
|
bool mapnulls[Natts_pg_ts_config_map];
|
|
|
|
memset(mapvalues, 0, sizeof(mapvalues));
|
|
memset(mapnulls, false, sizeof(mapnulls));
|
|
|
|
mapvalues[Anum_pg_ts_config_map_mapcfg - 1] = cfgOid;
|
|
mapvalues[Anum_pg_ts_config_map_maptokentype - 1] = cfgmap->maptokentype;
|
|
mapvalues[Anum_pg_ts_config_map_mapseqno - 1] = cfgmap->mapseqno;
|
|
mapvalues[Anum_pg_ts_config_map_mapdict - 1] = cfgmap->mapdict;
|
|
|
|
newmaptup = heap_form_tuple(mapRel->rd_att, mapvalues, mapnulls);
|
|
|
|
simple_heap_insert(mapRel, newmaptup);
|
|
|
|
CatalogUpdateIndexes(mapRel, newmaptup);
|
|
|
|
heap_freetuple(newmaptup);
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
}
|
|
|
|
makeConfigurationDependencies(tup, false, mapRel);
|
|
|
|
/* Post creation hook for new text search configuration */
|
|
InvokeObjectAccessHook(OAT_POST_CREATE, TSConfigRelationId, cfgOid, 0);
|
|
|
|
heap_freetuple(tup);
|
|
|
|
if (mapRel)
|
|
heap_close(mapRel, RowExclusiveLock);
|
|
heap_close(cfgRel, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH CONFIGURATION RENAME
|
|
*/
|
|
void
|
|
RenameTSConfiguration(List *oldname, const char *newname)
|
|
{
|
|
HeapTuple tup;
|
|
Relation rel;
|
|
Oid cfgId;
|
|
AclResult aclresult;
|
|
Oid namespaceOid;
|
|
|
|
rel = heap_open(TSConfigRelationId, RowExclusiveLock);
|
|
|
|
cfgId = get_ts_config_oid(oldname, false);
|
|
|
|
tup = SearchSysCacheCopy1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
|
|
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for text search configuration %u",
|
|
cfgId);
|
|
|
|
namespaceOid = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgnamespace;
|
|
|
|
if (SearchSysCacheExists2(TSCONFIGNAMENSP,
|
|
PointerGetDatum(newname),
|
|
ObjectIdGetDatum(namespaceOid)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("text search configuration \"%s\" already exists",
|
|
newname)));
|
|
|
|
/* must be owner */
|
|
if (!pg_ts_config_ownercheck(cfgId, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
|
|
NameListToString(oldname));
|
|
|
|
/* must have CREATE privilege on namespace */
|
|
aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);
|
|
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
|
|
get_namespace_name(namespaceOid));
|
|
|
|
namestrcpy(&(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname), newname);
|
|
simple_heap_update(rel, &tup->t_self, tup);
|
|
CatalogUpdateIndexes(rel, tup);
|
|
|
|
heap_close(rel, NoLock);
|
|
heap_freetuple(tup);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH CONFIGURATION any_name SET SCHEMA name
|
|
*/
|
|
void
|
|
AlterTSConfigurationNamespace(List *name, const char *newschema)
|
|
{
|
|
Oid cfgId,
|
|
nspOid;
|
|
Relation rel;
|
|
|
|
rel = heap_open(TSConfigRelationId, RowExclusiveLock);
|
|
|
|
cfgId = get_ts_config_oid(name, false);
|
|
|
|
/* get schema OID */
|
|
nspOid = LookupCreationNamespace(newschema);
|
|
|
|
AlterObjectNamespace(rel, TSCONFIGOID, TSCONFIGNAMENSP,
|
|
cfgId, nspOid,
|
|
Anum_pg_ts_config_cfgname,
|
|
Anum_pg_ts_config_cfgnamespace,
|
|
Anum_pg_ts_config_cfgowner,
|
|
ACL_KIND_TSCONFIGURATION);
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
}
|
|
|
|
Oid
|
|
AlterTSConfigurationNamespace_oid(Oid cfgId, Oid newNspOid)
|
|
{
|
|
Oid oldNspOid;
|
|
Relation rel;
|
|
|
|
rel = heap_open(TSConfigRelationId, RowExclusiveLock);
|
|
|
|
oldNspOid =
|
|
AlterObjectNamespace(rel, TSCONFIGOID, TSCONFIGNAMENSP,
|
|
cfgId, newNspOid,
|
|
Anum_pg_ts_config_cfgname,
|
|
Anum_pg_ts_config_cfgnamespace,
|
|
Anum_pg_ts_config_cfgowner,
|
|
ACL_KIND_TSCONFIGURATION);
|
|
|
|
heap_close(rel, RowExclusiveLock);
|
|
|
|
return oldNspOid;
|
|
}
|
|
|
|
/*
|
|
* DROP TEXT SEARCH CONFIGURATION
|
|
*/
|
|
void
|
|
RemoveTSConfigurations(DropStmt *drop)
|
|
{
|
|
ObjectAddresses *objects;
|
|
ListCell *cell;
|
|
|
|
/*
|
|
* First we identify all the objects, then we delete them in a single
|
|
* performMultipleDeletions() call. This is to avoid unwanted DROP
|
|
* RESTRICT errors if one of the objects depends on another.
|
|
*/
|
|
objects = new_object_addresses();
|
|
|
|
foreach(cell, drop->objects)
|
|
{
|
|
List *names = (List *) lfirst(cell);
|
|
Oid cfgOid;
|
|
Oid namespaceId;
|
|
ObjectAddress object;
|
|
HeapTuple tup;
|
|
|
|
tup = GetTSConfigTuple(names);
|
|
|
|
if (!HeapTupleIsValid(tup))
|
|
{
|
|
if (!drop->missing_ok)
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("text search configuration \"%s\" does not exist",
|
|
NameListToString(names))));
|
|
}
|
|
else
|
|
{
|
|
ereport(NOTICE,
|
|
(errmsg("text search configuration \"%s\" does not exist, skipping",
|
|
NameListToString(names))));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Permission check: must own configuration or its namespace */
|
|
cfgOid = HeapTupleGetOid(tup);
|
|
namespaceId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgnamespace;
|
|
if (!pg_ts_config_ownercheck(cfgOid, GetUserId()) &&
|
|
!pg_namespace_ownercheck(namespaceId, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
|
|
NameListToString(names));
|
|
|
|
object.classId = TSConfigRelationId;
|
|
object.objectId = cfgOid;
|
|
object.objectSubId = 0;
|
|
|
|
add_exact_object_address(&object, objects);
|
|
|
|
ReleaseSysCache(tup);
|
|
}
|
|
|
|
performMultipleDeletions(objects, drop->behavior);
|
|
|
|
free_object_addresses(objects);
|
|
}
|
|
|
|
/*
|
|
* Guts of TS configuration deletion.
|
|
*/
|
|
void
|
|
RemoveTSConfigurationById(Oid cfgId)
|
|
{
|
|
Relation relCfg,
|
|
relMap;
|
|
HeapTuple tup;
|
|
ScanKeyData skey;
|
|
SysScanDesc scan;
|
|
|
|
/* Remove the pg_ts_config entry */
|
|
relCfg = heap_open(TSConfigRelationId, RowExclusiveLock);
|
|
|
|
tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
|
|
|
|
if (!HeapTupleIsValid(tup))
|
|
elog(ERROR, "cache lookup failed for text search dictionary %u",
|
|
cfgId);
|
|
|
|
simple_heap_delete(relCfg, &tup->t_self);
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
heap_close(relCfg, RowExclusiveLock);
|
|
|
|
/* Remove any pg_ts_config_map entries */
|
|
relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock);
|
|
|
|
ScanKeyInit(&skey,
|
|
Anum_pg_ts_config_map_mapcfg,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(cfgId));
|
|
|
|
scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
|
|
SnapshotNow, 1, &skey);
|
|
|
|
while (HeapTupleIsValid((tup = systable_getnext(scan))))
|
|
{
|
|
simple_heap_delete(relMap, &tup->t_self);
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
|
|
heap_close(relMap, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH CONFIGURATION OWNER
|
|
*/
|
|
void
|
|
AlterTSConfigurationOwner(List *name, Oid newOwnerId)
|
|
{
|
|
HeapTuple tup;
|
|
Relation rel;
|
|
Oid cfgId;
|
|
AclResult aclresult;
|
|
Oid namespaceOid;
|
|
Form_pg_ts_config form;
|
|
|
|
rel = heap_open(TSConfigRelationId, RowExclusiveLock);
|
|
|
|
cfgId = get_ts_config_oid(name, false);
|
|
|
|
tup = SearchSysCacheCopy1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
|
|
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for text search configuration %u",
|
|
cfgId);
|
|
|
|
form = (Form_pg_ts_config) GETSTRUCT(tup);
|
|
namespaceOid = form->cfgnamespace;
|
|
|
|
if (form->cfgowner != newOwnerId)
|
|
{
|
|
/* Superusers can always do it */
|
|
if (!superuser())
|
|
{
|
|
/* must be owner */
|
|
if (!pg_ts_config_ownercheck(cfgId, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
|
|
NameListToString(name));
|
|
|
|
/* Must be able to become new owner */
|
|
check_is_member_of_role(GetUserId(), newOwnerId);
|
|
|
|
/* New owner must have CREATE privilege on namespace */
|
|
aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, ACL_CREATE);
|
|
if (aclresult != ACLCHECK_OK)
|
|
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
|
|
get_namespace_name(namespaceOid));
|
|
}
|
|
|
|
form->cfgowner = newOwnerId;
|
|
|
|
simple_heap_update(rel, &tup->t_self, tup);
|
|
CatalogUpdateIndexes(rel, tup);
|
|
|
|
/* Update owner dependency reference */
|
|
changeDependencyOnOwner(TSConfigRelationId, HeapTupleGetOid(tup),
|
|
newOwnerId);
|
|
}
|
|
|
|
heap_close(rel, NoLock);
|
|
heap_freetuple(tup);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH CONFIGURATION - main entry point
|
|
*/
|
|
void
|
|
AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
|
|
{
|
|
HeapTuple tup;
|
|
Relation relMap;
|
|
|
|
/* Find the configuration */
|
|
tup = GetTSConfigTuple(stmt->cfgname);
|
|
if (!HeapTupleIsValid(tup))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("text search configuration \"%s\" does not exist",
|
|
NameListToString(stmt->cfgname))));
|
|
|
|
/* must be owner */
|
|
if (!pg_ts_config_ownercheck(HeapTupleGetOid(tup), GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
|
|
NameListToString(stmt->cfgname));
|
|
|
|
relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock);
|
|
|
|
/* Add or drop mappings */
|
|
if (stmt->dicts)
|
|
MakeConfigurationMapping(stmt, tup, relMap);
|
|
else if (stmt->tokentype)
|
|
DropConfigurationMapping(stmt, tup, relMap);
|
|
|
|
/* Update dependencies */
|
|
makeConfigurationDependencies(tup, true, relMap);
|
|
|
|
heap_close(relMap, RowExclusiveLock);
|
|
|
|
ReleaseSysCache(tup);
|
|
}
|
|
|
|
/*
|
|
* Translate a list of token type names to an array of token type numbers
|
|
*/
|
|
static int *
|
|
getTokenTypes(Oid prsId, List *tokennames)
|
|
{
|
|
TSParserCacheEntry *prs = lookup_ts_parser_cache(prsId);
|
|
LexDescr *list;
|
|
int *res,
|
|
i,
|
|
ntoken;
|
|
ListCell *tn;
|
|
|
|
ntoken = list_length(tokennames);
|
|
if (ntoken == 0)
|
|
return NULL;
|
|
res = (int *) palloc(sizeof(int) * ntoken);
|
|
|
|
if (!OidIsValid(prs->lextypeOid))
|
|
elog(ERROR, "method lextype isn't defined for text search parser %u",
|
|
prsId);
|
|
|
|
/* lextype takes one dummy argument */
|
|
list = (LexDescr *) DatumGetPointer(OidFunctionCall1(prs->lextypeOid,
|
|
(Datum) 0));
|
|
|
|
i = 0;
|
|
foreach(tn, tokennames)
|
|
{
|
|
Value *val = (Value *) lfirst(tn);
|
|
bool found = false;
|
|
int j;
|
|
|
|
j = 0;
|
|
while (list && list[j].lexid)
|
|
{
|
|
/* XXX should we use pg_strcasecmp here? */
|
|
if (strcmp(strVal(val), list[j].alias) == 0)
|
|
{
|
|
res[i] = list[j].lexid;
|
|
found = true;
|
|
break;
|
|
}
|
|
j++;
|
|
}
|
|
if (!found)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("token type \"%s\" does not exist",
|
|
strVal(val))));
|
|
i++;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH CONFIGURATION ADD/ALTER MAPPING
|
|
*/
|
|
static void
|
|
MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
|
|
HeapTuple tup, Relation relMap)
|
|
{
|
|
Oid cfgId = HeapTupleGetOid(tup);
|
|
ScanKeyData skey[2];
|
|
SysScanDesc scan;
|
|
HeapTuple maptup;
|
|
int i;
|
|
int j;
|
|
Oid prsId;
|
|
int *tokens,
|
|
ntoken;
|
|
Oid *dictIds;
|
|
int ndict;
|
|
ListCell *c;
|
|
|
|
prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser;
|
|
|
|
tokens = getTokenTypes(prsId, stmt->tokentype);
|
|
ntoken = list_length(stmt->tokentype);
|
|
|
|
if (stmt->override)
|
|
{
|
|
/*
|
|
* delete maps for tokens if they exist and command was ALTER
|
|
*/
|
|
for (i = 0; i < ntoken; i++)
|
|
{
|
|
ScanKeyInit(&skey[0],
|
|
Anum_pg_ts_config_map_mapcfg,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(cfgId));
|
|
ScanKeyInit(&skey[1],
|
|
Anum_pg_ts_config_map_maptokentype,
|
|
BTEqualStrategyNumber, F_INT4EQ,
|
|
Int32GetDatum(tokens[i]));
|
|
|
|
scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
|
|
SnapshotNow, 2, skey);
|
|
|
|
while (HeapTupleIsValid((maptup = systable_getnext(scan))))
|
|
{
|
|
simple_heap_delete(relMap, &maptup->t_self);
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Convert list of dictionary names to array of dict OIDs
|
|
*/
|
|
ndict = list_length(stmt->dicts);
|
|
dictIds = (Oid *) palloc(sizeof(Oid) * ndict);
|
|
i = 0;
|
|
foreach(c, stmt->dicts)
|
|
{
|
|
List *names = (List *) lfirst(c);
|
|
|
|
dictIds[i] = get_ts_dict_oid(names, false);
|
|
i++;
|
|
}
|
|
|
|
if (stmt->replace)
|
|
{
|
|
/*
|
|
* Replace a specific dictionary in existing entries
|
|
*/
|
|
Oid dictOld = dictIds[0],
|
|
dictNew = dictIds[1];
|
|
|
|
ScanKeyInit(&skey[0],
|
|
Anum_pg_ts_config_map_mapcfg,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(cfgId));
|
|
|
|
scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
|
|
SnapshotNow, 1, skey);
|
|
|
|
while (HeapTupleIsValid((maptup = systable_getnext(scan))))
|
|
{
|
|
Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
|
|
|
|
/*
|
|
* check if it's one of target token types
|
|
*/
|
|
if (tokens)
|
|
{
|
|
bool tokmatch = false;
|
|
|
|
for (j = 0; j < ntoken; j++)
|
|
{
|
|
if (cfgmap->maptokentype == tokens[j])
|
|
{
|
|
tokmatch = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!tokmatch)
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* replace dictionary if match
|
|
*/
|
|
if (cfgmap->mapdict == dictOld)
|
|
{
|
|
Datum repl_val[Natts_pg_ts_config_map];
|
|
bool repl_null[Natts_pg_ts_config_map];
|
|
bool repl_repl[Natts_pg_ts_config_map];
|
|
HeapTuple newtup;
|
|
|
|
memset(repl_val, 0, sizeof(repl_val));
|
|
memset(repl_null, false, sizeof(repl_null));
|
|
memset(repl_repl, false, sizeof(repl_repl));
|
|
|
|
repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew);
|
|
repl_repl[Anum_pg_ts_config_map_mapdict - 1] = true;
|
|
|
|
newtup = heap_modify_tuple(maptup,
|
|
RelationGetDescr(relMap),
|
|
repl_val, repl_null, repl_repl);
|
|
simple_heap_update(relMap, &newtup->t_self, newtup);
|
|
|
|
CatalogUpdateIndexes(relMap, newtup);
|
|
}
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Insertion of new entries
|
|
*/
|
|
for (i = 0; i < ntoken; i++)
|
|
{
|
|
for (j = 0; j < ndict; j++)
|
|
{
|
|
Datum values[Natts_pg_ts_config_map];
|
|
bool nulls[Natts_pg_ts_config_map];
|
|
|
|
memset(nulls, false, sizeof(nulls));
|
|
values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId);
|
|
values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(tokens[i]);
|
|
values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1);
|
|
values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]);
|
|
|
|
tup = heap_form_tuple(relMap->rd_att, values, nulls);
|
|
simple_heap_insert(relMap, tup);
|
|
CatalogUpdateIndexes(relMap, tup);
|
|
|
|
heap_freetuple(tup);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH CONFIGURATION DROP MAPPING
|
|
*/
|
|
static void
|
|
DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
|
|
HeapTuple tup, Relation relMap)
|
|
{
|
|
Oid cfgId = HeapTupleGetOid(tup);
|
|
ScanKeyData skey[2];
|
|
SysScanDesc scan;
|
|
HeapTuple maptup;
|
|
int i;
|
|
Oid prsId;
|
|
int *tokens;
|
|
ListCell *c;
|
|
|
|
prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser;
|
|
|
|
tokens = getTokenTypes(prsId, stmt->tokentype);
|
|
|
|
i = 0;
|
|
foreach(c, stmt->tokentype)
|
|
{
|
|
Value *val = (Value *) lfirst(c);
|
|
bool found = false;
|
|
|
|
ScanKeyInit(&skey[0],
|
|
Anum_pg_ts_config_map_mapcfg,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(cfgId));
|
|
ScanKeyInit(&skey[1],
|
|
Anum_pg_ts_config_map_maptokentype,
|
|
BTEqualStrategyNumber, F_INT4EQ,
|
|
Int32GetDatum(tokens[i]));
|
|
|
|
scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
|
|
SnapshotNow, 2, skey);
|
|
|
|
while (HeapTupleIsValid((maptup = systable_getnext(scan))))
|
|
{
|
|
simple_heap_delete(relMap, &maptup->t_self);
|
|
found = true;
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
|
|
if (!found)
|
|
{
|
|
if (!stmt->missing_ok)
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("mapping for token type \"%s\" does not exist",
|
|
strVal(val))));
|
|
}
|
|
else
|
|
{
|
|
ereport(NOTICE,
|
|
(errmsg("mapping for token type \"%s\" does not exist, skipping",
|
|
strVal(val))));
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 = cstring_to_text_with_len(buf.data, buf.len);
|
|
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\"",
|
|
text_to_cstring(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\"",
|
|
text_to_cstring(in))));
|
|
|
|
pfree(workspace);
|
|
|
|
return result;
|
|
}
|