mirror of
https://github.com/postgres/postgres.git
synced 2025-07-20 05:03:10 +03:00
Previously tables declared WITH OIDS, including a significant fraction of the catalog tables, stored the oid column not as a normal column, but as part of the tuple header. This special column was not shown by default, which was somewhat odd, as it's often (consider e.g. pg_class.oid) one of the more important parts of a row. Neither pg_dump nor COPY included the contents of the oid column by default. The fact that the oid column was not an ordinary column necessitated a significant amount of special case code to support oid columns. That already was painful for the existing, but upcoming work aiming to make table storage pluggable, would have required expanding and duplicating that "specialness" significantly. WITH OIDS has been deprecated since 2005 (commit ff02d0a05280e0). Remove it. Removing includes: - CREATE TABLE and ALTER TABLE syntax for declaring the table to be WITH OIDS has been removed (WITH (oids[ = true]) will error out) - pg_dump does not support dumping tables declared WITH OIDS and will issue a warning when dumping one (and ignore the oid column). - restoring an pg_dump archive with pg_restore will warn when restoring a table with oid contents (and ignore the oid column) - COPY will refuse to load binary dump that includes oids. - pg_upgrade will error out when encountering tables declared WITH OIDS, they have to be altered to remove the oid column first. - Functionality to access the oid of the last inserted row (like plpgsql's RESULT_OID, spi's SPI_lastoid, ...) has been removed. The syntax for declaring a table WITHOUT OIDS (or WITH (oids = false) for CREATE TABLE) is still supported. While that requires a bit of support code, it seems unnecessary to break applications / dumps that do not use oids, and are explicit about not using them. The biggest user of WITH OID columns was postgres' catalog. This commit changes all 'magic' oid columns to be columns that are normally declared and stored. To reduce unnecessary query breakage all the newly added columns are still named 'oid', even if a table's column naming scheme would indicate 'reloid' or such. This obviously requires adapting a lot code, mostly replacing oid access via HeapTupleGetOid() with access to the underlying Form_pg_*->oid column. The bootstrap process now assigns oids for all oid columns in genbki.pl that do not have an explicit value (starting at the largest oid previously used), only oids assigned later by oids will be above FirstBootstrapObjectId. As the oid column now is a normal column the special bootstrap syntax for oids has been removed. Oids are not automatically assigned during insertion anymore, all backend code explicitly assigns oids with GetNewOidWithIndex(). For the rare case that insertions into the catalog via SQL are called for the new pg_nextoid() function can be used (which only works on catalog tables). The fact that oid columns on system tables are now normal columns means that they will be included in the set of columns expanded by * (i.e. SELECT * FROM pg_class will now include the table's oid, previously it did not). It'd not technically be hard to hide oid column by default, but that'd mean confusing behavior would either have to be carried forward forever, or it'd cause breakage down the line. While it's not unlikely that further adjustments are needed, the scope/invasiveness of the patch makes it worthwhile to get merge this now. It's painful to maintain externally, too complicated to commit after the code code freeze, and a dependency of a number of other patches. Catversion bump, for obvious reasons. Author: Andres Freund, with contributions by John Naylor Discussion: https://postgr.es/m/20180930034810.ywp2c7awz7opzcfr@alap3.anarazel.de
1783 lines
43 KiB
C
1783 lines
43 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* tsearchcmds.c
|
|
*
|
|
* Routines for tsearch manipulation commands
|
|
*
|
|
* Portions Copyright (c) 1996-2018, 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/genam.h"
|
|
#include "access/heapam.h"
|
|
#include "access/htup_details.h"
|
|
#include "access/xact.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/dependency.h"
|
|
#include "catalog/indexing.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 "commands/event_trigger.h"
|
|
#include "miscadmin.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "parser/parse_func.h"
|
|
#include "tsearch/ts_cache.h"
|
|
#include "tsearch/ts_utils.h"
|
|
#include "utils/builtins.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
|
|
*
|
|
* Return value is the address of said new entry.
|
|
*/
|
|
static ObjectAddress
|
|
makeParserDependencies(HeapTuple tuple)
|
|
{
|
|
Form_pg_ts_parser prs = (Form_pg_ts_parser) GETSTRUCT(tuple);
|
|
ObjectAddress myself,
|
|
referenced;
|
|
|
|
myself.classId = TSParserRelationId;
|
|
myself.objectId = prs->oid;
|
|
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, false);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
return myself;
|
|
}
|
|
|
|
/*
|
|
* CREATE TEXT SEARCH PARSER
|
|
*/
|
|
ObjectAddress
|
|
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;
|
|
ObjectAddress address;
|
|
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to create text search parsers")));
|
|
|
|
prsRel = heap_open(TSParserRelationId, RowExclusiveLock);
|
|
|
|
/* 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));
|
|
|
|
prsOid = GetNewOidWithIndex(prsRel, TSParserOidIndexId,
|
|
Anum_pg_ts_parser_oid);
|
|
values[Anum_pg_ts_parser_oid - 1] = ObjectIdGetDatum(prsOid);
|
|
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 (strcmp(defel->defname, "start") == 0)
|
|
{
|
|
values[Anum_pg_ts_parser_prsstart - 1] =
|
|
get_ts_parser_func(defel, Anum_pg_ts_parser_prsstart);
|
|
}
|
|
else if (strcmp(defel->defname, "gettoken") == 0)
|
|
{
|
|
values[Anum_pg_ts_parser_prstoken - 1] =
|
|
get_ts_parser_func(defel, Anum_pg_ts_parser_prstoken);
|
|
}
|
|
else if (strcmp(defel->defname, "end") == 0)
|
|
{
|
|
values[Anum_pg_ts_parser_prsend - 1] =
|
|
get_ts_parser_func(defel, Anum_pg_ts_parser_prsend);
|
|
}
|
|
else if (strcmp(defel->defname, "headline") == 0)
|
|
{
|
|
values[Anum_pg_ts_parser_prsheadline - 1] =
|
|
get_ts_parser_func(defel, Anum_pg_ts_parser_prsheadline);
|
|
}
|
|
else if (strcmp(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
|
|
*/
|
|
tup = heap_form_tuple(prsRel->rd_att, values, nulls);
|
|
|
|
CatalogTupleInsert(prsRel, tup);
|
|
|
|
address = makeParserDependencies(tup);
|
|
|
|
/* Post creation hook for new text search parser */
|
|
InvokeObjectPostCreateHook(TSParserRelationId, prsOid, 0);
|
|
|
|
heap_freetuple(tup);
|
|
|
|
heap_close(prsRel, RowExclusiveLock);
|
|
|
|
return address;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
|
|
CatalogTupleDelete(relation, &tup->t_self);
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
heap_close(relation, RowExclusiveLock);
|
|
}
|
|
|
|
/* ---------------------- TS Dictionary commands -----------------------*/
|
|
|
|
/*
|
|
* make pg_depend entries for a new pg_ts_dict entry
|
|
*
|
|
* Return value is address of the new entry
|
|
*/
|
|
static ObjectAddress
|
|
makeDictionaryDependencies(HeapTuple tuple)
|
|
{
|
|
Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tuple);
|
|
ObjectAddress myself,
|
|
referenced;
|
|
|
|
myself.classId = TSDictionaryRelationId;
|
|
myself.objectId = dict->oid;
|
|
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, false);
|
|
|
|
/* dependency on template */
|
|
referenced.classId = TSTemplateRelationId;
|
|
referenced.objectId = dict->dicttemplate;
|
|
referenced.objectSubId = 0;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
return myself;
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
ObjectAddress
|
|
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;
|
|
ObjectAddress address;
|
|
|
|
/* 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, OBJECT_SCHEMA,
|
|
get_namespace_name(namespaceoid));
|
|
|
|
/*
|
|
* loop over the definition list and extract the information we need.
|
|
*/
|
|
foreach(pl, parameters)
|
|
{
|
|
DefElem *defel = (DefElem *) lfirst(pl);
|
|
|
|
if (strcmp(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);
|
|
|
|
|
|
dictRel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
|
|
|
|
/*
|
|
* Looks good, insert
|
|
*/
|
|
memset(values, 0, sizeof(values));
|
|
memset(nulls, false, sizeof(nulls));
|
|
|
|
dictOid = GetNewOidWithIndex(dictRel, TSDictionaryOidIndexId,
|
|
Anum_pg_ts_dict_oid);
|
|
values[Anum_pg_ts_dict_oid - 1] = ObjectIdGetDatum(dictOid);
|
|
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;
|
|
|
|
tup = heap_form_tuple(dictRel->rd_att, values, nulls);
|
|
|
|
CatalogTupleInsert(dictRel, tup);
|
|
|
|
address = makeDictionaryDependencies(tup);
|
|
|
|
/* Post creation hook for new text search dictionary */
|
|
InvokeObjectPostCreateHook(TSDictionaryRelationId, dictOid, 0);
|
|
|
|
heap_freetuple(tup);
|
|
|
|
heap_close(dictRel, RowExclusiveLock);
|
|
|
|
return address;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
|
|
CatalogTupleDelete(relation, &tup->t_self);
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
heap_close(relation, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH DICTIONARY
|
|
*/
|
|
ObjectAddress
|
|
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];
|
|
ObjectAddress address;
|
|
|
|
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, OBJECT_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 (strcmp(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);
|
|
|
|
CatalogTupleUpdate(rel, &newtup->t_self, newtup);
|
|
|
|
InvokeObjectPostAlterHook(TSDictionaryRelationId, dictId, 0);
|
|
|
|
ObjectAddressSet(address, TSDictionaryRelationId, dictId);
|
|
|
|
/*
|
|
* 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);
|
|
|
|
return address;
|
|
}
|
|
|
|
/* ---------------------- 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 ObjectAddress
|
|
makeTSTemplateDependencies(HeapTuple tuple)
|
|
{
|
|
Form_pg_ts_template tmpl = (Form_pg_ts_template) GETSTRUCT(tuple);
|
|
ObjectAddress myself,
|
|
referenced;
|
|
|
|
myself.classId = TSTemplateRelationId;
|
|
myself.objectId = tmpl->oid;
|
|
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, false);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
return myself;
|
|
}
|
|
|
|
/*
|
|
* CREATE TEXT SEARCH TEMPLATE
|
|
*/
|
|
ObjectAddress
|
|
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 tmplOid;
|
|
Oid namespaceoid;
|
|
char *tmplname;
|
|
ObjectAddress address;
|
|
|
|
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);
|
|
|
|
tmplRel = heap_open(TSTemplateRelationId, RowExclusiveLock);
|
|
|
|
for (i = 0; i < Natts_pg_ts_template; i++)
|
|
{
|
|
nulls[i] = false;
|
|
values[i] = ObjectIdGetDatum(InvalidOid);
|
|
}
|
|
|
|
tmplOid = GetNewOidWithIndex(tmplRel, TSTemplateOidIndexId,
|
|
Anum_pg_ts_dict_oid);
|
|
values[Anum_pg_ts_template_oid - 1] = ObjectIdGetDatum(tmplOid);
|
|
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 (strcmp(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 (strcmp(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
|
|
*/
|
|
tup = heap_form_tuple(tmplRel->rd_att, values, nulls);
|
|
|
|
CatalogTupleInsert(tmplRel, tup);
|
|
|
|
address = makeTSTemplateDependencies(tup);
|
|
|
|
/* Post creation hook for new text search template */
|
|
InvokeObjectPostCreateHook(TSTemplateRelationId, tmplOid, 0);
|
|
|
|
heap_freetuple(tup);
|
|
|
|
heap_close(tmplRel, RowExclusiveLock);
|
|
|
|
return address;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
|
|
CatalogTupleDelete(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 ObjectAddress
|
|
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 = cfg->oid;
|
|
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 */
|
|
recordDependencyOnCurrentExtension(&myself, removeOld);
|
|
|
|
/* 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,
|
|
NULL, 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);
|
|
|
|
return myself;
|
|
}
|
|
|
|
/*
|
|
* CREATE TEXT SEARCH CONFIGURATION
|
|
*/
|
|
ObjectAddress
|
|
DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied)
|
|
{
|
|
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;
|
|
ObjectAddress address;
|
|
|
|
/* 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, OBJECT_SCHEMA,
|
|
get_namespace_name(namespaceoid));
|
|
|
|
/*
|
|
* loop over the definition list and extract the information we need.
|
|
*/
|
|
foreach(pl, parameters)
|
|
{
|
|
DefElem *defel = (DefElem *) lfirst(pl);
|
|
|
|
if (strcmp(defel->defname, "parser") == 0)
|
|
prsOid = get_ts_parser_oid(defGetQualifiedName(defel), false);
|
|
else if (strcmp(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")));
|
|
|
|
/* make copied tsconfig available to callers */
|
|
if (copied && OidIsValid(sourceOid))
|
|
{
|
|
ObjectAddressSet(*copied,
|
|
TSConfigRelationId,
|
|
sourceOid);
|
|
}
|
|
|
|
/*
|
|
* 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")));
|
|
|
|
cfgRel = heap_open(TSConfigRelationId, RowExclusiveLock);
|
|
|
|
/*
|
|
* Looks good, build tuple and insert
|
|
*/
|
|
memset(values, 0, sizeof(values));
|
|
memset(nulls, false, sizeof(nulls));
|
|
|
|
cfgOid = GetNewOidWithIndex(cfgRel, TSConfigOidIndexId,
|
|
Anum_pg_ts_config_oid);
|
|
values[Anum_pg_ts_config_oid - 1] = ObjectIdGetDatum(cfgOid);
|
|
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);
|
|
|
|
tup = heap_form_tuple(cfgRel->rd_att, values, nulls);
|
|
|
|
CatalogTupleInsert(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,
|
|
NULL, 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);
|
|
|
|
CatalogTupleInsert(mapRel, newmaptup);
|
|
|
|
heap_freetuple(newmaptup);
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
}
|
|
|
|
address = makeConfigurationDependencies(tup, false, mapRel);
|
|
|
|
/* Post creation hook for new text search configuration */
|
|
InvokeObjectPostCreateHook(TSConfigRelationId, cfgOid, 0);
|
|
|
|
heap_freetuple(tup);
|
|
|
|
if (mapRel)
|
|
heap_close(mapRel, RowExclusiveLock);
|
|
heap_close(cfgRel, RowExclusiveLock);
|
|
|
|
return address;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
|
|
CatalogTupleDelete(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,
|
|
NULL, 1, &skey);
|
|
|
|
while (HeapTupleIsValid((tup = systable_getnext(scan))))
|
|
{
|
|
CatalogTupleDelete(relMap, &tup->t_self);
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
|
|
heap_close(relMap, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH CONFIGURATION - main entry point
|
|
*/
|
|
ObjectAddress
|
|
AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
|
|
{
|
|
HeapTuple tup;
|
|
Oid cfgId;
|
|
Relation relMap;
|
|
ObjectAddress address;
|
|
|
|
/* 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))));
|
|
|
|
cfgId = ((Form_pg_ts_config) GETSTRUCT(tup))->oid;
|
|
|
|
/* must be owner */
|
|
if (!pg_ts_config_ownercheck(cfgId, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_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);
|
|
|
|
InvokeObjectPostAlterHook(TSConfigRelationId, cfgId, 0);
|
|
|
|
ObjectAddressSet(address, TSConfigRelationId, cfgId);
|
|
|
|
heap_close(relMap, RowExclusiveLock);
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
return address;
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
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)
|
|
{
|
|
Form_pg_ts_config tsform;
|
|
Oid cfgId;
|
|
ScanKeyData skey[2];
|
|
SysScanDesc scan;
|
|
HeapTuple maptup;
|
|
int i;
|
|
int j;
|
|
Oid prsId;
|
|
int *tokens,
|
|
ntoken;
|
|
Oid *dictIds;
|
|
int ndict;
|
|
ListCell *c;
|
|
|
|
tsform = (Form_pg_ts_config) GETSTRUCT(tup);
|
|
cfgId = tsform->oid;
|
|
prsId = tsform->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,
|
|
NULL, 2, skey);
|
|
|
|
while (HeapTupleIsValid((maptup = systable_getnext(scan))))
|
|
{
|
|
CatalogTupleDelete(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,
|
|
NULL, 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);
|
|
CatalogTupleUpdate(relMap, &newtup->t_self, 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);
|
|
CatalogTupleInsert(relMap, tup);
|
|
|
|
heap_freetuple(tup);
|
|
}
|
|
}
|
|
}
|
|
|
|
EventTriggerCollectAlterTSConfig(stmt, cfgId, dictIds, ndict);
|
|
}
|
|
|
|
/*
|
|
* ALTER TEXT SEARCH CONFIGURATION DROP MAPPING
|
|
*/
|
|
static void
|
|
DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
|
|
HeapTuple tup, Relation relMap)
|
|
{
|
|
Form_pg_ts_config tsform;
|
|
Oid cfgId;
|
|
ScanKeyData skey[2];
|
|
SysScanDesc scan;
|
|
HeapTuple maptup;
|
|
int i;
|
|
Oid prsId;
|
|
int *tokens;
|
|
ListCell *c;
|
|
|
|
tsform = (Form_pg_ts_config) GETSTRUCT(tup);
|
|
cfgId = tsform->oid;
|
|
prsId = tsform->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,
|
|
NULL, 2, skey);
|
|
|
|
while (HeapTupleIsValid((maptup = systable_getnext(scan))))
|
|
{
|
|
CatalogTupleDelete(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++;
|
|
}
|
|
|
|
EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)
|
|
appendStringInfoString(&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 = DatumGetTextPP(txt); /* in case it's toasted */
|
|
List *result = NIL;
|
|
int len = VARSIZE_ANY_EXHDR(in);
|
|
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_ANY(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)), -1));
|
|
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)), -1));
|
|
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)), -1));
|
|
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)), -1));
|
|
}
|
|
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;
|
|
}
|