LCOV - code coverage report
Current view: top level - src/backend/commands - tsearchcmds.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 619 704 87.9 %
Date: 2024-04-24 07:11:10 Functions: 22 22 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * tsearchcmds.c
       4             :  *
       5             :  *    Routines for tsearch manipulation commands
       6             :  *
       7             :  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
       8             :  * Portions Copyright (c) 1994, Regents of the University of California
       9             :  *
      10             :  *
      11             :  * IDENTIFICATION
      12             :  *    src/backend/commands/tsearchcmds.c
      13             :  *
      14             :  *-------------------------------------------------------------------------
      15             :  */
      16             : #include "postgres.h"
      17             : 
      18             : #include <ctype.h>
      19             : 
      20             : #include "access/genam.h"
      21             : #include "access/htup_details.h"
      22             : #include "access/table.h"
      23             : #include "access/xact.h"
      24             : #include "catalog/catalog.h"
      25             : #include "catalog/dependency.h"
      26             : #include "catalog/indexing.h"
      27             : #include "catalog/objectaccess.h"
      28             : #include "catalog/pg_namespace.h"
      29             : #include "catalog/pg_proc.h"
      30             : #include "catalog/pg_ts_config.h"
      31             : #include "catalog/pg_ts_config_map.h"
      32             : #include "catalog/pg_ts_dict.h"
      33             : #include "catalog/pg_ts_parser.h"
      34             : #include "catalog/pg_ts_template.h"
      35             : #include "catalog/pg_type.h"
      36             : #include "commands/defrem.h"
      37             : #include "commands/event_trigger.h"
      38             : #include "common/string.h"
      39             : #include "miscadmin.h"
      40             : #include "nodes/makefuncs.h"
      41             : #include "parser/parse_func.h"
      42             : #include "tsearch/ts_cache.h"
      43             : #include "tsearch/ts_public.h"
      44             : #include "utils/acl.h"
      45             : #include "utils/builtins.h"
      46             : #include "utils/fmgroids.h"
      47             : #include "utils/lsyscache.h"
      48             : #include "utils/rel.h"
      49             : #include "utils/syscache.h"
      50             : 
      51             : /* Single entry of List returned by getTokenTypes() */
      52             : typedef struct
      53             : {
      54             :     int         num;            /* token type number */
      55             :     char       *name;           /* token type name */
      56             : } TSTokenTypeItem;
      57             : 
      58             : static void MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
      59             :                                      HeapTuple tup, Relation relMap);
      60             : static void DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
      61             :                                      HeapTuple tup, Relation relMap);
      62             : static DefElem *buildDefItem(const char *name, const char *val,
      63             :                              bool was_quoted);
      64             : 
      65             : 
      66             : /* --------------------- TS Parser commands ------------------------ */
      67             : 
      68             : /*
      69             :  * lookup a parser support function and return its OID (as a Datum)
      70             :  *
      71             :  * attnum is the pg_ts_parser column the function will go into
      72             :  */
      73             : static Datum
      74         138 : get_ts_parser_func(DefElem *defel, int attnum)
      75             : {
      76         138 :     List       *funcName = defGetQualifiedName(defel);
      77             :     Oid         typeId[3];
      78             :     Oid         retTypeId;
      79             :     int         nargs;
      80             :     Oid         procOid;
      81             : 
      82         138 :     retTypeId = INTERNALOID;    /* correct for most */
      83         138 :     typeId[0] = INTERNALOID;
      84         138 :     switch (attnum)
      85             :     {
      86          34 :         case Anum_pg_ts_parser_prsstart:
      87          34 :             nargs = 2;
      88          34 :             typeId[1] = INT4OID;
      89          34 :             break;
      90          34 :         case Anum_pg_ts_parser_prstoken:
      91          34 :             nargs = 3;
      92          34 :             typeId[1] = INTERNALOID;
      93          34 :             typeId[2] = INTERNALOID;
      94          34 :             break;
      95          34 :         case Anum_pg_ts_parser_prsend:
      96          34 :             nargs = 1;
      97          34 :             retTypeId = VOIDOID;
      98          34 :             break;
      99           2 :         case Anum_pg_ts_parser_prsheadline:
     100           2 :             nargs = 3;
     101           2 :             typeId[1] = INTERNALOID;
     102           2 :             typeId[2] = TSQUERYOID;
     103           2 :             break;
     104          34 :         case Anum_pg_ts_parser_prslextype:
     105          34 :             nargs = 1;
     106             : 
     107             :             /*
     108             :              * Note: because the lextype method returns type internal, it must
     109             :              * have an internal-type argument for security reasons.  The
     110             :              * argument is not actually used, but is just passed as a zero.
     111             :              */
     112          34 :             break;
     113           0 :         default:
     114             :             /* should not be here */
     115           0 :             elog(ERROR, "unrecognized attribute for text search parser: %d",
     116             :                  attnum);
     117             :             nargs = 0;          /* keep compiler quiet */
     118             :     }
     119             : 
     120         138 :     procOid = LookupFuncName(funcName, nargs, typeId, false);
     121         138 :     if (get_func_rettype(procOid) != retTypeId)
     122           0 :         ereport(ERROR,
     123             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     124             :                  errmsg("function %s should return type %s",
     125             :                         func_signature_string(funcName, nargs, NIL, typeId),
     126             :                         format_type_be(retTypeId))));
     127             : 
     128         138 :     return ObjectIdGetDatum(procOid);
     129             : }
     130             : 
     131             : /*
     132             :  * make pg_depend entries for a new pg_ts_parser entry
     133             :  *
     134             :  * Return value is the address of said new entry.
     135             :  */
     136             : static ObjectAddress
     137          34 : makeParserDependencies(HeapTuple tuple)
     138             : {
     139          34 :     Form_pg_ts_parser prs = (Form_pg_ts_parser) GETSTRUCT(tuple);
     140             :     ObjectAddress myself,
     141             :                 referenced;
     142             :     ObjectAddresses *addrs;
     143             : 
     144          34 :     ObjectAddressSet(myself, TSParserRelationId, prs->oid);
     145             : 
     146             :     /* dependency on extension */
     147          34 :     recordDependencyOnCurrentExtension(&myself, false);
     148             : 
     149          34 :     addrs = new_object_addresses();
     150             : 
     151             :     /* dependency on namespace */
     152          34 :     ObjectAddressSet(referenced, NamespaceRelationId, prs->prsnamespace);
     153          34 :     add_exact_object_address(&referenced, addrs);
     154             : 
     155             :     /* dependencies on functions */
     156          34 :     ObjectAddressSet(referenced, ProcedureRelationId, prs->prsstart);
     157          34 :     add_exact_object_address(&referenced, addrs);
     158             : 
     159          34 :     referenced.objectId = prs->prstoken;
     160          34 :     add_exact_object_address(&referenced, addrs);
     161             : 
     162          34 :     referenced.objectId = prs->prsend;
     163          34 :     add_exact_object_address(&referenced, addrs);
     164             : 
     165          34 :     referenced.objectId = prs->prslextype;
     166          34 :     add_exact_object_address(&referenced, addrs);
     167             : 
     168          34 :     if (OidIsValid(prs->prsheadline))
     169             :     {
     170           2 :         referenced.objectId = prs->prsheadline;
     171           2 :         add_exact_object_address(&referenced, addrs);
     172             :     }
     173             : 
     174          34 :     record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
     175          34 :     free_object_addresses(addrs);
     176             : 
     177          34 :     return myself;
     178             : }
     179             : 
     180             : /*
     181             :  * CREATE TEXT SEARCH PARSER
     182             :  */
     183             : ObjectAddress
     184          40 : DefineTSParser(List *names, List *parameters)
     185             : {
     186             :     char       *prsname;
     187             :     ListCell   *pl;
     188             :     Relation    prsRel;
     189             :     HeapTuple   tup;
     190             :     Datum       values[Natts_pg_ts_parser];
     191             :     bool        nulls[Natts_pg_ts_parser];
     192             :     NameData    pname;
     193             :     Oid         prsOid;
     194             :     Oid         namespaceoid;
     195             :     ObjectAddress address;
     196             : 
     197          40 :     if (!superuser())
     198           0 :         ereport(ERROR,
     199             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     200             :                  errmsg("must be superuser to create text search parsers")));
     201             : 
     202          40 :     prsRel = table_open(TSParserRelationId, RowExclusiveLock);
     203             : 
     204             :     /* Convert list of names to a name and namespace */
     205          40 :     namespaceoid = QualifiedNameGetCreationNamespace(names, &prsname);
     206             : 
     207             :     /* initialize tuple fields with name/namespace */
     208          40 :     memset(values, 0, sizeof(values));
     209          40 :     memset(nulls, false, sizeof(nulls));
     210             : 
     211          40 :     prsOid = GetNewOidWithIndex(prsRel, TSParserOidIndexId,
     212             :                                 Anum_pg_ts_parser_oid);
     213          40 :     values[Anum_pg_ts_parser_oid - 1] = ObjectIdGetDatum(prsOid);
     214          40 :     namestrcpy(&pname, prsname);
     215          40 :     values[Anum_pg_ts_parser_prsname - 1] = NameGetDatum(&pname);
     216          40 :     values[Anum_pg_ts_parser_prsnamespace - 1] = ObjectIdGetDatum(namespaceoid);
     217             : 
     218             :     /*
     219             :      * loop over the definition list and extract the information we need.
     220             :      */
     221         178 :     foreach(pl, parameters)
     222             :     {
     223         144 :         DefElem    *defel = (DefElem *) lfirst(pl);
     224             : 
     225         144 :         if (strcmp(defel->defname, "start") == 0)
     226             :         {
     227          34 :             values[Anum_pg_ts_parser_prsstart - 1] =
     228          34 :                 get_ts_parser_func(defel, Anum_pg_ts_parser_prsstart);
     229             :         }
     230         110 :         else if (strcmp(defel->defname, "gettoken") == 0)
     231             :         {
     232          34 :             values[Anum_pg_ts_parser_prstoken - 1] =
     233          34 :                 get_ts_parser_func(defel, Anum_pg_ts_parser_prstoken);
     234             :         }
     235          76 :         else if (strcmp(defel->defname, "end") == 0)
     236             :         {
     237          34 :             values[Anum_pg_ts_parser_prsend - 1] =
     238          34 :                 get_ts_parser_func(defel, Anum_pg_ts_parser_prsend);
     239             :         }
     240          42 :         else if (strcmp(defel->defname, "headline") == 0)
     241             :         {
     242           2 :             values[Anum_pg_ts_parser_prsheadline - 1] =
     243           2 :                 get_ts_parser_func(defel, Anum_pg_ts_parser_prsheadline);
     244             :         }
     245          40 :         else if (strcmp(defel->defname, "lextypes") == 0)
     246             :         {
     247          34 :             values[Anum_pg_ts_parser_prslextype - 1] =
     248          34 :                 get_ts_parser_func(defel, Anum_pg_ts_parser_prslextype);
     249             :         }
     250             :         else
     251           6 :             ereport(ERROR,
     252             :                     (errcode(ERRCODE_SYNTAX_ERROR),
     253             :                      errmsg("text search parser parameter \"%s\" not recognized",
     254             :                             defel->defname)));
     255             :     }
     256             : 
     257             :     /*
     258             :      * Validation
     259             :      */
     260          34 :     if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsstart - 1])))
     261           0 :         ereport(ERROR,
     262             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     263             :                  errmsg("text search parser start method is required")));
     264             : 
     265          34 :     if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prstoken - 1])))
     266           0 :         ereport(ERROR,
     267             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     268             :                  errmsg("text search parser gettoken method is required")));
     269             : 
     270          34 :     if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsend - 1])))
     271           0 :         ereport(ERROR,
     272             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     273             :                  errmsg("text search parser end method is required")));
     274             : 
     275          34 :     if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prslextype - 1])))
     276           0 :         ereport(ERROR,
     277             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     278             :                  errmsg("text search parser lextypes method is required")));
     279             : 
     280             :     /*
     281             :      * Looks good, insert
     282             :      */
     283          34 :     tup = heap_form_tuple(prsRel->rd_att, values, nulls);
     284             : 
     285          34 :     CatalogTupleInsert(prsRel, tup);
     286             : 
     287          34 :     address = makeParserDependencies(tup);
     288             : 
     289             :     /* Post creation hook for new text search parser */
     290          34 :     InvokeObjectPostCreateHook(TSParserRelationId, prsOid, 0);
     291             : 
     292          34 :     heap_freetuple(tup);
     293             : 
     294          34 :     table_close(prsRel, RowExclusiveLock);
     295             : 
     296          34 :     return address;
     297             : }
     298             : 
     299             : /* ---------------------- TS Dictionary commands -----------------------*/
     300             : 
     301             : /*
     302             :  * make pg_depend entries for a new pg_ts_dict entry
     303             :  *
     304             :  * Return value is address of the new entry
     305             :  */
     306             : static ObjectAddress
     307        2250 : makeDictionaryDependencies(HeapTuple tuple)
     308             : {
     309        2250 :     Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tuple);
     310             :     ObjectAddress myself,
     311             :                 referenced;
     312             :     ObjectAddresses *addrs;
     313             : 
     314        2250 :     ObjectAddressSet(myself, TSDictionaryRelationId, dict->oid);
     315             : 
     316             :     /* dependency on owner */
     317        2250 :     recordDependencyOnOwner(myself.classId, myself.objectId, dict->dictowner);
     318             : 
     319             :     /* dependency on extension */
     320        2250 :     recordDependencyOnCurrentExtension(&myself, false);
     321             : 
     322        2250 :     addrs = new_object_addresses();
     323             : 
     324             :     /* dependency on namespace */
     325        2250 :     ObjectAddressSet(referenced, NamespaceRelationId, dict->dictnamespace);
     326        2250 :     add_exact_object_address(&referenced, addrs);
     327             : 
     328             :     /* dependency on template */
     329        2250 :     ObjectAddressSet(referenced, TSTemplateRelationId, dict->dicttemplate);
     330        2250 :     add_exact_object_address(&referenced, addrs);
     331             : 
     332        2250 :     record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
     333        2250 :     free_object_addresses(addrs);
     334             : 
     335        2250 :     return myself;
     336             : }
     337             : 
     338             : /*
     339             :  * verify that a template's init method accepts a proposed option list
     340             :  */
     341             : static void
     342        2314 : verify_dictoptions(Oid tmplId, List *dictoptions)
     343             : {
     344             :     HeapTuple   tup;
     345             :     Form_pg_ts_template tform;
     346             :     Oid         initmethod;
     347             : 
     348             :     /*
     349             :      * Suppress this test when running in a standalone backend.  This is a
     350             :      * hack to allow initdb to create prefab dictionaries that might not
     351             :      * actually be usable in template1's encoding (due to using external files
     352             :      * that can't be translated into template1's encoding).  We want to create
     353             :      * them anyway, since they might be usable later in other databases.
     354             :      */
     355        2314 :     if (!IsUnderPostmaster)
     356        2128 :         return;
     357             : 
     358         186 :     tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId));
     359         186 :     if (!HeapTupleIsValid(tup)) /* should not happen */
     360           0 :         elog(ERROR, "cache lookup failed for text search template %u",
     361             :              tmplId);
     362         186 :     tform = (Form_pg_ts_template) GETSTRUCT(tup);
     363             : 
     364         186 :     initmethod = tform->tmplinit;
     365             : 
     366         186 :     if (!OidIsValid(initmethod))
     367             :     {
     368             :         /* If there is no init method, disallow any options */
     369           0 :         if (dictoptions)
     370           0 :             ereport(ERROR,
     371             :                     (errcode(ERRCODE_SYNTAX_ERROR),
     372             :                      errmsg("text search template \"%s\" does not accept options",
     373             :                             NameStr(tform->tmplname))));
     374             :     }
     375             :     else
     376             :     {
     377             :         /*
     378             :          * Copy the options just in case init method thinks it can scribble on
     379             :          * them ...
     380             :          */
     381         186 :         dictoptions = copyObject(dictoptions);
     382             : 
     383             :         /*
     384             :          * Call the init method and see if it complains.  We don't worry about
     385             :          * it leaking memory, since our command will soon be over anyway.
     386             :          */
     387         186 :         (void) OidFunctionCall1(initmethod, PointerGetDatum(dictoptions));
     388             :     }
     389             : 
     390         154 :     ReleaseSysCache(tup);
     391             : }
     392             : 
     393             : /*
     394             :  * CREATE TEXT SEARCH DICTIONARY
     395             :  */
     396             : ObjectAddress
     397        2274 : DefineTSDictionary(List *names, List *parameters)
     398             : {
     399             :     ListCell   *pl;
     400             :     Relation    dictRel;
     401             :     HeapTuple   tup;
     402             :     Datum       values[Natts_pg_ts_dict];
     403             :     bool        nulls[Natts_pg_ts_dict];
     404             :     NameData    dname;
     405        2274 :     Oid         templId = InvalidOid;
     406        2274 :     List       *dictoptions = NIL;
     407             :     Oid         dictOid;
     408             :     Oid         namespaceoid;
     409             :     AclResult   aclresult;
     410             :     char       *dictname;
     411             :     ObjectAddress address;
     412             : 
     413             :     /* Convert list of names to a name and namespace */
     414        2274 :     namespaceoid = QualifiedNameGetCreationNamespace(names, &dictname);
     415             : 
     416             :     /* Check we have creation rights in target namespace */
     417        2274 :     aclresult = object_aclcheck(NamespaceRelationId, namespaceoid, GetUserId(), ACL_CREATE);
     418        2274 :     if (aclresult != ACLCHECK_OK)
     419           0 :         aclcheck_error(aclresult, OBJECT_SCHEMA,
     420           0 :                        get_namespace_name(namespaceoid));
     421             : 
     422             :     /*
     423             :      * loop over the definition list and extract the information we need.
     424             :      */
     425        8016 :     foreach(pl, parameters)
     426             :     {
     427        5742 :         DefElem    *defel = (DefElem *) lfirst(pl);
     428             : 
     429        5742 :         if (strcmp(defel->defname, "template") == 0)
     430             :         {
     431        2274 :             templId = get_ts_template_oid(defGetQualifiedName(defel), false);
     432             :         }
     433             :         else
     434             :         {
     435             :             /* Assume it's an option for the dictionary itself */
     436        3468 :             dictoptions = lappend(dictoptions, defel);
     437             :         }
     438             :     }
     439             : 
     440             :     /*
     441             :      * Validation
     442             :      */
     443        2274 :     if (!OidIsValid(templId))
     444           0 :         ereport(ERROR,
     445             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     446             :                  errmsg("text search template is required")));
     447             : 
     448        2274 :     verify_dictoptions(templId, dictoptions);
     449             : 
     450             : 
     451        2250 :     dictRel = table_open(TSDictionaryRelationId, RowExclusiveLock);
     452             : 
     453             :     /*
     454             :      * Looks good, insert
     455             :      */
     456        2250 :     memset(values, 0, sizeof(values));
     457        2250 :     memset(nulls, false, sizeof(nulls));
     458             : 
     459        2250 :     dictOid = GetNewOidWithIndex(dictRel, TSDictionaryOidIndexId,
     460             :                                  Anum_pg_ts_dict_oid);
     461        2250 :     values[Anum_pg_ts_dict_oid - 1] = ObjectIdGetDatum(dictOid);
     462        2250 :     namestrcpy(&dname, dictname);
     463        2250 :     values[Anum_pg_ts_dict_dictname - 1] = NameGetDatum(&dname);
     464        2250 :     values[Anum_pg_ts_dict_dictnamespace - 1] = ObjectIdGetDatum(namespaceoid);
     465        2250 :     values[Anum_pg_ts_dict_dictowner - 1] = ObjectIdGetDatum(GetUserId());
     466        2250 :     values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(templId);
     467        2250 :     if (dictoptions)
     468        2208 :         values[Anum_pg_ts_dict_dictinitoption - 1] =
     469        2208 :             PointerGetDatum(serialize_deflist(dictoptions));
     470             :     else
     471          42 :         nulls[Anum_pg_ts_dict_dictinitoption - 1] = true;
     472             : 
     473        2250 :     tup = heap_form_tuple(dictRel->rd_att, values, nulls);
     474             : 
     475        2250 :     CatalogTupleInsert(dictRel, tup);
     476             : 
     477        2250 :     address = makeDictionaryDependencies(tup);
     478             : 
     479             :     /* Post creation hook for new text search dictionary */
     480        2250 :     InvokeObjectPostCreateHook(TSDictionaryRelationId, dictOid, 0);
     481             : 
     482        2250 :     heap_freetuple(tup);
     483             : 
     484        2250 :     table_close(dictRel, RowExclusiveLock);
     485             : 
     486        2250 :     return address;
     487             : }
     488             : 
     489             : /*
     490             :  * ALTER TEXT SEARCH DICTIONARY
     491             :  */
     492             : ObjectAddress
     493          40 : AlterTSDictionary(AlterTSDictionaryStmt *stmt)
     494             : {
     495             :     HeapTuple   tup,
     496             :                 newtup;
     497             :     Relation    rel;
     498             :     Oid         dictId;
     499             :     ListCell   *pl;
     500             :     List       *dictoptions;
     501             :     Datum       opt;
     502             :     bool        isnull;
     503             :     Datum       repl_val[Natts_pg_ts_dict];
     504             :     bool        repl_null[Natts_pg_ts_dict];
     505             :     bool        repl_repl[Natts_pg_ts_dict];
     506             :     ObjectAddress address;
     507             : 
     508          40 :     dictId = get_ts_dict_oid(stmt->dictname, false);
     509             : 
     510          40 :     rel = table_open(TSDictionaryRelationId, RowExclusiveLock);
     511             : 
     512          40 :     tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
     513             : 
     514          40 :     if (!HeapTupleIsValid(tup))
     515           0 :         elog(ERROR, "cache lookup failed for text search dictionary %u",
     516             :              dictId);
     517             : 
     518             :     /* must be owner */
     519          40 :     if (!object_ownercheck(TSDictionaryRelationId, dictId, GetUserId()))
     520           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TSDICTIONARY,
     521           0 :                        NameListToString(stmt->dictname));
     522             : 
     523             :     /* deserialize the existing set of options */
     524          40 :     opt = SysCacheGetAttr(TSDICTOID, tup,
     525             :                           Anum_pg_ts_dict_dictinitoption,
     526             :                           &isnull);
     527          40 :     if (isnull)
     528           6 :         dictoptions = NIL;
     529             :     else
     530          34 :         dictoptions = deserialize_deflist(opt);
     531             : 
     532             :     /*
     533             :      * Modify the options list as per specified changes
     534             :      */
     535         136 :     foreach(pl, stmt->options)
     536             :     {
     537          96 :         DefElem    *defel = (DefElem *) lfirst(pl);
     538             :         ListCell   *cell;
     539             : 
     540             :         /*
     541             :          * Remove any matches ...
     542             :          */
     543         452 :         foreach(cell, dictoptions)
     544             :         {
     545         356 :             DefElem    *oldel = (DefElem *) lfirst(cell);
     546             : 
     547         356 :             if (strcmp(oldel->defname, defel->defname) == 0)
     548          72 :                 dictoptions = foreach_delete_current(dictoptions, cell);
     549             :         }
     550             : 
     551             :         /*
     552             :          * and add new value if it's got one
     553             :          */
     554          96 :         if (defel->arg)
     555          96 :             dictoptions = lappend(dictoptions, defel);
     556             :     }
     557             : 
     558             :     /*
     559             :      * Validate
     560             :      */
     561          40 :     verify_dictoptions(((Form_pg_ts_dict) GETSTRUCT(tup))->dicttemplate,
     562             :                        dictoptions);
     563             : 
     564             :     /*
     565             :      * Looks good, update
     566             :      */
     567          32 :     memset(repl_val, 0, sizeof(repl_val));
     568          32 :     memset(repl_null, false, sizeof(repl_null));
     569          32 :     memset(repl_repl, false, sizeof(repl_repl));
     570             : 
     571          32 :     if (dictoptions)
     572          32 :         repl_val[Anum_pg_ts_dict_dictinitoption - 1] =
     573          32 :             PointerGetDatum(serialize_deflist(dictoptions));
     574             :     else
     575           0 :         repl_null[Anum_pg_ts_dict_dictinitoption - 1] = true;
     576          32 :     repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = true;
     577             : 
     578          32 :     newtup = heap_modify_tuple(tup, RelationGetDescr(rel),
     579             :                                repl_val, repl_null, repl_repl);
     580             : 
     581          32 :     CatalogTupleUpdate(rel, &newtup->t_self, newtup);
     582             : 
     583          32 :     InvokeObjectPostAlterHook(TSDictionaryRelationId, dictId, 0);
     584             : 
     585          32 :     ObjectAddressSet(address, TSDictionaryRelationId, dictId);
     586             : 
     587             :     /*
     588             :      * NOTE: because we only support altering the options, not the template,
     589             :      * there is no need to update dependencies.  This might have to change if
     590             :      * the options ever reference inside-the-database objects.
     591             :      */
     592             : 
     593          32 :     heap_freetuple(newtup);
     594          32 :     ReleaseSysCache(tup);
     595             : 
     596          32 :     table_close(rel, RowExclusiveLock);
     597             : 
     598          32 :     return address;
     599             : }
     600             : 
     601             : /* ---------------------- TS Template commands -----------------------*/
     602             : 
     603             : /*
     604             :  * lookup a template support function and return its OID (as a Datum)
     605             :  *
     606             :  * attnum is the pg_ts_template column the function will go into
     607             :  */
     608             : static Datum
     609         202 : get_ts_template_func(DefElem *defel, int attnum)
     610             : {
     611         202 :     List       *funcName = defGetQualifiedName(defel);
     612             :     Oid         typeId[4];
     613             :     Oid         retTypeId;
     614             :     int         nargs;
     615             :     Oid         procOid;
     616             : 
     617         202 :     retTypeId = INTERNALOID;
     618         202 :     typeId[0] = INTERNALOID;
     619         202 :     typeId[1] = INTERNALOID;
     620         202 :     typeId[2] = INTERNALOID;
     621         202 :     typeId[3] = INTERNALOID;
     622         202 :     switch (attnum)
     623             :     {
     624          88 :         case Anum_pg_ts_template_tmplinit:
     625          88 :             nargs = 1;
     626          88 :             break;
     627         114 :         case Anum_pg_ts_template_tmpllexize:
     628         114 :             nargs = 4;
     629         114 :             break;
     630           0 :         default:
     631             :             /* should not be here */
     632           0 :             elog(ERROR, "unrecognized attribute for text search template: %d",
     633             :                  attnum);
     634             :             nargs = 0;          /* keep compiler quiet */
     635             :     }
     636             : 
     637         202 :     procOid = LookupFuncName(funcName, nargs, typeId, false);
     638         202 :     if (get_func_rettype(procOid) != retTypeId)
     639           0 :         ereport(ERROR,
     640             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     641             :                  errmsg("function %s should return type %s",
     642             :                         func_signature_string(funcName, nargs, NIL, typeId),
     643             :                         format_type_be(retTypeId))));
     644             : 
     645         202 :     return ObjectIdGetDatum(procOid);
     646             : }
     647             : 
     648             : /*
     649             :  * make pg_depend entries for a new pg_ts_template entry
     650             :  */
     651             : static ObjectAddress
     652         114 : makeTSTemplateDependencies(HeapTuple tuple)
     653             : {
     654         114 :     Form_pg_ts_template tmpl = (Form_pg_ts_template) GETSTRUCT(tuple);
     655             :     ObjectAddress myself,
     656             :                 referenced;
     657             :     ObjectAddresses *addrs;
     658             : 
     659         114 :     ObjectAddressSet(myself, TSTemplateRelationId, tmpl->oid);
     660             : 
     661             :     /* dependency on extension */
     662         114 :     recordDependencyOnCurrentExtension(&myself, false);
     663             : 
     664         114 :     addrs = new_object_addresses();
     665             : 
     666             :     /* dependency on namespace */
     667         114 :     ObjectAddressSet(referenced, NamespaceRelationId, tmpl->tmplnamespace);
     668         114 :     add_exact_object_address(&referenced, addrs);
     669             : 
     670             :     /* dependencies on functions */
     671         114 :     ObjectAddressSet(referenced, ProcedureRelationId, tmpl->tmpllexize);
     672         114 :     add_exact_object_address(&referenced, addrs);
     673             : 
     674         114 :     if (OidIsValid(tmpl->tmplinit))
     675             :     {
     676          88 :         referenced.objectId = tmpl->tmplinit;
     677          88 :         add_exact_object_address(&referenced, addrs);
     678             :     }
     679             : 
     680         114 :     record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
     681         114 :     free_object_addresses(addrs);
     682             : 
     683         114 :     return myself;
     684             : }
     685             : 
     686             : /*
     687             :  * CREATE TEXT SEARCH TEMPLATE
     688             :  */
     689             : ObjectAddress
     690         120 : DefineTSTemplate(List *names, List *parameters)
     691             : {
     692             :     ListCell   *pl;
     693             :     Relation    tmplRel;
     694             :     HeapTuple   tup;
     695             :     Datum       values[Natts_pg_ts_template];
     696             :     bool        nulls[Natts_pg_ts_template];
     697             :     NameData    dname;
     698             :     int         i;
     699             :     Oid         tmplOid;
     700             :     Oid         namespaceoid;
     701             :     char       *tmplname;
     702             :     ObjectAddress address;
     703             : 
     704         120 :     if (!superuser())
     705           0 :         ereport(ERROR,
     706             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     707             :                  errmsg("must be superuser to create text search templates")));
     708             : 
     709             :     /* Convert list of names to a name and namespace */
     710         120 :     namespaceoid = QualifiedNameGetCreationNamespace(names, &tmplname);
     711             : 
     712         120 :     tmplRel = table_open(TSTemplateRelationId, RowExclusiveLock);
     713             : 
     714         720 :     for (i = 0; i < Natts_pg_ts_template; i++)
     715             :     {
     716         600 :         nulls[i] = false;
     717         600 :         values[i] = ObjectIdGetDatum(InvalidOid);
     718             :     }
     719             : 
     720         120 :     tmplOid = GetNewOidWithIndex(tmplRel, TSTemplateOidIndexId,
     721             :                                  Anum_pg_ts_dict_oid);
     722         120 :     values[Anum_pg_ts_template_oid - 1] = ObjectIdGetDatum(tmplOid);
     723         120 :     namestrcpy(&dname, tmplname);
     724         120 :     values[Anum_pg_ts_template_tmplname - 1] = NameGetDatum(&dname);
     725         120 :     values[Anum_pg_ts_template_tmplnamespace - 1] = ObjectIdGetDatum(namespaceoid);
     726             : 
     727             :     /*
     728             :      * loop over the definition list and extract the information we need.
     729             :      */
     730         322 :     foreach(pl, parameters)
     731             :     {
     732         208 :         DefElem    *defel = (DefElem *) lfirst(pl);
     733             : 
     734         208 :         if (strcmp(defel->defname, "init") == 0)
     735             :         {
     736          88 :             values[Anum_pg_ts_template_tmplinit - 1] =
     737          88 :                 get_ts_template_func(defel, Anum_pg_ts_template_tmplinit);
     738          88 :             nulls[Anum_pg_ts_template_tmplinit - 1] = false;
     739             :         }
     740         120 :         else if (strcmp(defel->defname, "lexize") == 0)
     741             :         {
     742         114 :             values[Anum_pg_ts_template_tmpllexize - 1] =
     743         114 :                 get_ts_template_func(defel, Anum_pg_ts_template_tmpllexize);
     744         114 :             nulls[Anum_pg_ts_template_tmpllexize - 1] = false;
     745             :         }
     746             :         else
     747           6 :             ereport(ERROR,
     748             :                     (errcode(ERRCODE_SYNTAX_ERROR),
     749             :                      errmsg("text search template parameter \"%s\" not recognized",
     750             :                             defel->defname)));
     751             :     }
     752             : 
     753             :     /*
     754             :      * Validation
     755             :      */
     756         114 :     if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_template_tmpllexize - 1])))
     757           0 :         ereport(ERROR,
     758             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     759             :                  errmsg("text search template lexize method is required")));
     760             : 
     761             :     /*
     762             :      * Looks good, insert
     763             :      */
     764         114 :     tup = heap_form_tuple(tmplRel->rd_att, values, nulls);
     765             : 
     766         114 :     CatalogTupleInsert(tmplRel, tup);
     767             : 
     768         114 :     address = makeTSTemplateDependencies(tup);
     769             : 
     770             :     /* Post creation hook for new text search template */
     771         114 :     InvokeObjectPostCreateHook(TSTemplateRelationId, tmplOid, 0);
     772             : 
     773         114 :     heap_freetuple(tup);
     774             : 
     775         114 :     table_close(tmplRel, RowExclusiveLock);
     776             : 
     777         114 :     return address;
     778             : }
     779             : 
     780             : /* ---------------------- TS Configuration commands -----------------------*/
     781             : 
     782             : /*
     783             :  * Finds syscache tuple of configuration.
     784             :  * Returns NULL if no such cfg.
     785             :  */
     786             : static HeapTuple
     787        6624 : GetTSConfigTuple(List *names)
     788             : {
     789             :     HeapTuple   tup;
     790             :     Oid         cfgId;
     791             : 
     792        6624 :     cfgId = get_ts_config_oid(names, true);
     793        6624 :     if (!OidIsValid(cfgId))
     794           0 :         return NULL;
     795             : 
     796        6624 :     tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
     797             : 
     798        6624 :     if (!HeapTupleIsValid(tup)) /* should not happen */
     799           0 :         elog(ERROR, "cache lookup failed for text search configuration %u",
     800             :              cfgId);
     801             : 
     802        6624 :     return tup;
     803             : }
     804             : 
     805             : /*
     806             :  * make pg_depend entries for a new or updated pg_ts_config entry
     807             :  *
     808             :  * Pass opened pg_ts_config_map relation if there might be any config map
     809             :  * entries for the config.
     810             :  */
     811             : static ObjectAddress
     812        8816 : makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
     813             :                               Relation mapRel)
     814             : {
     815        8816 :     Form_pg_ts_config cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
     816             :     ObjectAddresses *addrs;
     817             :     ObjectAddress myself,
     818             :                 referenced;
     819             : 
     820        8816 :     myself.classId = TSConfigRelationId;
     821        8816 :     myself.objectId = cfg->oid;
     822        8816 :     myself.objectSubId = 0;
     823             : 
     824             :     /* for ALTER case, first flush old dependencies, except extension deps */
     825        8816 :     if (removeOld)
     826             :     {
     827        6600 :         deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
     828        6600 :         deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0);
     829             :     }
     830             : 
     831             :     /*
     832             :      * We use an ObjectAddresses list to remove possible duplicate
     833             :      * dependencies from the config map info.  The pg_ts_config items
     834             :      * shouldn't be duplicates, but might as well fold them all into one call.
     835             :      */
     836        8816 :     addrs = new_object_addresses();
     837             : 
     838             :     /* dependency on namespace */
     839        8816 :     referenced.classId = NamespaceRelationId;
     840        8816 :     referenced.objectId = cfg->cfgnamespace;
     841        8816 :     referenced.objectSubId = 0;
     842        8816 :     add_exact_object_address(&referenced, addrs);
     843             : 
     844             :     /* dependency on owner */
     845        8816 :     recordDependencyOnOwner(myself.classId, myself.objectId, cfg->cfgowner);
     846             : 
     847             :     /* dependency on extension */
     848        8816 :     recordDependencyOnCurrentExtension(&myself, removeOld);
     849             : 
     850             :     /* dependency on parser */
     851        8816 :     referenced.classId = TSParserRelationId;
     852        8816 :     referenced.objectId = cfg->cfgparser;
     853        8816 :     referenced.objectSubId = 0;
     854        8816 :     add_exact_object_address(&referenced, addrs);
     855             : 
     856             :     /* dependencies on dictionaries listed in config map */
     857        8816 :     if (mapRel)
     858             :     {
     859             :         ScanKeyData skey;
     860             :         SysScanDesc scan;
     861             :         HeapTuple   maptup;
     862             : 
     863             :         /* CCI to ensure we can see effects of caller's changes */
     864        6672 :         CommandCounterIncrement();
     865             : 
     866        6672 :         ScanKeyInit(&skey,
     867             :                     Anum_pg_ts_config_map_mapcfg,
     868             :                     BTEqualStrategyNumber, F_OIDEQ,
     869             :                     ObjectIdGetDatum(myself.objectId));
     870             : 
     871        6672 :         scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
     872             :                                   NULL, 1, &skey);
     873             : 
     874      113844 :         while (HeapTupleIsValid((maptup = systable_getnext(scan))))
     875             :         {
     876      107172 :             Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
     877             : 
     878      107172 :             referenced.classId = TSDictionaryRelationId;
     879      107172 :             referenced.objectId = cfgmap->mapdict;
     880      107172 :             referenced.objectSubId = 0;
     881      107172 :             add_exact_object_address(&referenced, addrs);
     882             :         }
     883             : 
     884        6672 :         systable_endscan(scan);
     885             :     }
     886             : 
     887             :     /* Record 'em (this includes duplicate elimination) */
     888        8816 :     record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
     889             : 
     890        8816 :     free_object_addresses(addrs);
     891             : 
     892        8816 :     return myself;
     893             : }
     894             : 
     895             : /*
     896             :  * CREATE TEXT SEARCH CONFIGURATION
     897             :  */
     898             : ObjectAddress
     899        2216 : DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied)
     900             : {
     901             :     Relation    cfgRel;
     902        2216 :     Relation    mapRel = NULL;
     903             :     HeapTuple   tup;
     904             :     Datum       values[Natts_pg_ts_config];
     905             :     bool        nulls[Natts_pg_ts_config];
     906             :     AclResult   aclresult;
     907             :     Oid         namespaceoid;
     908             :     char       *cfgname;
     909             :     NameData    cname;
     910        2216 :     Oid         sourceOid = InvalidOid;
     911        2216 :     Oid         prsOid = InvalidOid;
     912             :     Oid         cfgOid;
     913             :     ListCell   *pl;
     914             :     ObjectAddress address;
     915             : 
     916             :     /* Convert list of names to a name and namespace */
     917        2216 :     namespaceoid = QualifiedNameGetCreationNamespace(names, &cfgname);
     918             : 
     919             :     /* Check we have creation rights in target namespace */
     920        2216 :     aclresult = object_aclcheck(NamespaceRelationId, namespaceoid, GetUserId(), ACL_CREATE);
     921        2216 :     if (aclresult != ACLCHECK_OK)
     922           0 :         aclcheck_error(aclresult, OBJECT_SCHEMA,
     923           0 :                        get_namespace_name(namespaceoid));
     924             : 
     925             :     /*
     926             :      * loop over the definition list and extract the information we need.
     927             :      */
     928        4432 :     foreach(pl, parameters)
     929             :     {
     930        2216 :         DefElem    *defel = (DefElem *) lfirst(pl);
     931             : 
     932        2216 :         if (strcmp(defel->defname, "parser") == 0)
     933        2144 :             prsOid = get_ts_parser_oid(defGetQualifiedName(defel), false);
     934          72 :         else if (strcmp(defel->defname, "copy") == 0)
     935          72 :             sourceOid = get_ts_config_oid(defGetQualifiedName(defel), false);
     936             :         else
     937           0 :             ereport(ERROR,
     938             :                     (errcode(ERRCODE_SYNTAX_ERROR),
     939             :                      errmsg("text search configuration parameter \"%s\" not recognized",
     940             :                             defel->defname)));
     941             :     }
     942             : 
     943        2216 :     if (OidIsValid(sourceOid) && OidIsValid(prsOid))
     944           0 :         ereport(ERROR,
     945             :                 (errcode(ERRCODE_SYNTAX_ERROR),
     946             :                  errmsg("cannot specify both PARSER and COPY options")));
     947             : 
     948             :     /* make copied tsconfig available to callers */
     949        2216 :     if (copied && OidIsValid(sourceOid))
     950             :     {
     951          72 :         ObjectAddressSet(*copied,
     952             :                          TSConfigRelationId,
     953             :                          sourceOid);
     954             :     }
     955             : 
     956             :     /*
     957             :      * Look up source config if given.
     958             :      */
     959        2216 :     if (OidIsValid(sourceOid))
     960             :     {
     961             :         Form_pg_ts_config cfg;
     962             : 
     963          72 :         tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(sourceOid));
     964          72 :         if (!HeapTupleIsValid(tup))
     965           0 :             elog(ERROR, "cache lookup failed for text search configuration %u",
     966             :                  sourceOid);
     967             : 
     968          72 :         cfg = (Form_pg_ts_config) GETSTRUCT(tup);
     969             : 
     970             :         /* use source's parser */
     971          72 :         prsOid = cfg->cfgparser;
     972             : 
     973          72 :         ReleaseSysCache(tup);
     974             :     }
     975             : 
     976             :     /*
     977             :      * Validation
     978             :      */
     979        2216 :     if (!OidIsValid(prsOid))
     980           0 :         ereport(ERROR,
     981             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     982             :                  errmsg("text search parser is required")));
     983             : 
     984        2216 :     cfgRel = table_open(TSConfigRelationId, RowExclusiveLock);
     985             : 
     986             :     /*
     987             :      * Looks good, build tuple and insert
     988             :      */
     989        2216 :     memset(values, 0, sizeof(values));
     990        2216 :     memset(nulls, false, sizeof(nulls));
     991             : 
     992        2216 :     cfgOid = GetNewOidWithIndex(cfgRel, TSConfigOidIndexId,
     993             :                                 Anum_pg_ts_config_oid);
     994        2216 :     values[Anum_pg_ts_config_oid - 1] = ObjectIdGetDatum(cfgOid);
     995        2216 :     namestrcpy(&cname, cfgname);
     996        2216 :     values[Anum_pg_ts_config_cfgname - 1] = NameGetDatum(&cname);
     997        2216 :     values[Anum_pg_ts_config_cfgnamespace - 1] = ObjectIdGetDatum(namespaceoid);
     998        2216 :     values[Anum_pg_ts_config_cfgowner - 1] = ObjectIdGetDatum(GetUserId());
     999        2216 :     values[Anum_pg_ts_config_cfgparser - 1] = ObjectIdGetDatum(prsOid);
    1000             : 
    1001        2216 :     tup = heap_form_tuple(cfgRel->rd_att, values, nulls);
    1002             : 
    1003        2216 :     CatalogTupleInsert(cfgRel, tup);
    1004             : 
    1005        2216 :     if (OidIsValid(sourceOid))
    1006             :     {
    1007             :         /*
    1008             :          * Copy token-dicts map from source config
    1009             :          */
    1010             :         ScanKeyData skey;
    1011             :         SysScanDesc scan;
    1012             :         HeapTuple   maptup;
    1013             :         TupleDesc   mapDesc;
    1014             :         TupleTableSlot **slot;
    1015             :         CatalogIndexState indstate;
    1016             :         int         max_slots,
    1017             :                     slot_init_count,
    1018             :                     slot_stored_count;
    1019             : 
    1020          72 :         mapRel = table_open(TSConfigMapRelationId, RowExclusiveLock);
    1021          72 :         mapDesc = RelationGetDescr(mapRel);
    1022             : 
    1023          72 :         indstate = CatalogOpenIndexes(mapRel);
    1024             : 
    1025             :         /*
    1026             :          * Allocate the slots to use, but delay costly initialization until we
    1027             :          * know that they will be used.
    1028             :          */
    1029          72 :         max_slots = MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_ts_config_map);
    1030          72 :         slot = palloc(sizeof(TupleTableSlot *) * max_slots);
    1031             : 
    1032          72 :         ScanKeyInit(&skey,
    1033             :                     Anum_pg_ts_config_map_mapcfg,
    1034             :                     BTEqualStrategyNumber, F_OIDEQ,
    1035             :                     ObjectIdGetDatum(sourceOid));
    1036             : 
    1037          72 :         scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
    1038             :                                   NULL, 1, &skey);
    1039             : 
    1040             :         /* number of slots currently storing tuples */
    1041          72 :         slot_stored_count = 0;
    1042             :         /* number of slots currently initialized */
    1043          72 :         slot_init_count = 0;
    1044             : 
    1045        1512 :         while (HeapTupleIsValid((maptup = systable_getnext(scan))))
    1046             :         {
    1047        1440 :             Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
    1048             : 
    1049        1440 :             if (slot_init_count < max_slots)
    1050             :             {
    1051        1440 :                 slot[slot_stored_count] = MakeSingleTupleTableSlot(mapDesc,
    1052             :                                                                    &TTSOpsHeapTuple);
    1053        1440 :                 slot_init_count++;
    1054             :             }
    1055             : 
    1056        1440 :             ExecClearTuple(slot[slot_stored_count]);
    1057             : 
    1058        1440 :             memset(slot[slot_stored_count]->tts_isnull, false,
    1059        1440 :                    slot[slot_stored_count]->tts_tupleDescriptor->natts * sizeof(bool));
    1060             : 
    1061        1440 :             slot[slot_stored_count]->tts_values[Anum_pg_ts_config_map_mapcfg - 1] = cfgOid;
    1062        1440 :             slot[slot_stored_count]->tts_values[Anum_pg_ts_config_map_maptokentype - 1] = cfgmap->maptokentype;
    1063        1440 :             slot[slot_stored_count]->tts_values[Anum_pg_ts_config_map_mapseqno - 1] = cfgmap->mapseqno;
    1064        1440 :             slot[slot_stored_count]->tts_values[Anum_pg_ts_config_map_mapdict - 1] = cfgmap->mapdict;
    1065             : 
    1066        1440 :             ExecStoreVirtualTuple(slot[slot_stored_count]);
    1067        1440 :             slot_stored_count++;
    1068             : 
    1069             :             /* If slots are full, insert a batch of tuples */
    1070        1440 :             if (slot_stored_count == max_slots)
    1071             :             {
    1072           0 :                 CatalogTuplesMultiInsertWithInfo(mapRel, slot, slot_stored_count,
    1073             :                                                  indstate);
    1074           0 :                 slot_stored_count = 0;
    1075             :             }
    1076             :         }
    1077             : 
    1078             :         /* Insert any tuples left in the buffer */
    1079          72 :         if (slot_stored_count > 0)
    1080          72 :             CatalogTuplesMultiInsertWithInfo(mapRel, slot, slot_stored_count,
    1081             :                                              indstate);
    1082             : 
    1083        1512 :         for (int i = 0; i < slot_init_count; i++)
    1084        1440 :             ExecDropSingleTupleTableSlot(slot[i]);
    1085             : 
    1086          72 :         systable_endscan(scan);
    1087          72 :         CatalogCloseIndexes(indstate);
    1088             :     }
    1089             : 
    1090        2216 :     address = makeConfigurationDependencies(tup, false, mapRel);
    1091             : 
    1092             :     /* Post creation hook for new text search configuration */
    1093        2216 :     InvokeObjectPostCreateHook(TSConfigRelationId, cfgOid, 0);
    1094             : 
    1095        2216 :     heap_freetuple(tup);
    1096             : 
    1097        2216 :     if (mapRel)
    1098          72 :         table_close(mapRel, RowExclusiveLock);
    1099        2216 :     table_close(cfgRel, RowExclusiveLock);
    1100             : 
    1101        2216 :     return address;
    1102             : }
    1103             : 
    1104             : /*
    1105             :  * Guts of TS configuration deletion.
    1106             :  */
    1107             : void
    1108          48 : RemoveTSConfigurationById(Oid cfgId)
    1109             : {
    1110             :     Relation    relCfg,
    1111             :                 relMap;
    1112             :     HeapTuple   tup;
    1113             :     ScanKeyData skey;
    1114             :     SysScanDesc scan;
    1115             : 
    1116             :     /* Remove the pg_ts_config entry */
    1117          48 :     relCfg = table_open(TSConfigRelationId, RowExclusiveLock);
    1118             : 
    1119          48 :     tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
    1120             : 
    1121          48 :     if (!HeapTupleIsValid(tup))
    1122           0 :         elog(ERROR, "cache lookup failed for text search dictionary %u",
    1123             :              cfgId);
    1124             : 
    1125          48 :     CatalogTupleDelete(relCfg, &tup->t_self);
    1126             : 
    1127          48 :     ReleaseSysCache(tup);
    1128             : 
    1129          48 :     table_close(relCfg, RowExclusiveLock);
    1130             : 
    1131             :     /* Remove any pg_ts_config_map entries */
    1132          48 :     relMap = table_open(TSConfigMapRelationId, RowExclusiveLock);
    1133             : 
    1134          48 :     ScanKeyInit(&skey,
    1135             :                 Anum_pg_ts_config_map_mapcfg,
    1136             :                 BTEqualStrategyNumber, F_OIDEQ,
    1137             :                 ObjectIdGetDatum(cfgId));
    1138             : 
    1139          48 :     scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
    1140             :                               NULL, 1, &skey);
    1141             : 
    1142         846 :     while (HeapTupleIsValid((tup = systable_getnext(scan))))
    1143             :     {
    1144         798 :         CatalogTupleDelete(relMap, &tup->t_self);
    1145             :     }
    1146             : 
    1147          48 :     systable_endscan(scan);
    1148             : 
    1149          48 :     table_close(relMap, RowExclusiveLock);
    1150          48 : }
    1151             : 
    1152             : /*
    1153             :  * ALTER TEXT SEARCH CONFIGURATION - main entry point
    1154             :  */
    1155             : ObjectAddress
    1156        6624 : AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
    1157             : {
    1158             :     HeapTuple   tup;
    1159             :     Oid         cfgId;
    1160             :     Relation    relMap;
    1161             :     ObjectAddress address;
    1162             : 
    1163             :     /* Find the configuration */
    1164        6624 :     tup = GetTSConfigTuple(stmt->cfgname);
    1165        6624 :     if (!HeapTupleIsValid(tup))
    1166           0 :         ereport(ERROR,
    1167             :                 (errcode(ERRCODE_UNDEFINED_OBJECT),
    1168             :                  errmsg("text search configuration \"%s\" does not exist",
    1169             :                         NameListToString(stmt->cfgname))));
    1170             : 
    1171        6624 :     cfgId = ((Form_pg_ts_config) GETSTRUCT(tup))->oid;
    1172             : 
    1173             :     /* must be owner */
    1174        6624 :     if (!object_ownercheck(TSConfigRelationId, cfgId, GetUserId()))
    1175           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TSCONFIGURATION,
    1176           0 :                        NameListToString(stmt->cfgname));
    1177             : 
    1178        6624 :     relMap = table_open(TSConfigMapRelationId, RowExclusiveLock);
    1179             : 
    1180             :     /* Add or drop mappings */
    1181        6624 :     if (stmt->dicts)
    1182        6594 :         MakeConfigurationMapping(stmt, tup, relMap);
    1183          30 :     else if (stmt->tokentype)
    1184          30 :         DropConfigurationMapping(stmt, tup, relMap);
    1185             : 
    1186             :     /* Update dependencies */
    1187        6600 :     makeConfigurationDependencies(tup, true, relMap);
    1188             : 
    1189        6600 :     InvokeObjectPostAlterHook(TSConfigRelationId, cfgId, 0);
    1190             : 
    1191        6600 :     ObjectAddressSet(address, TSConfigRelationId, cfgId);
    1192             : 
    1193        6600 :     table_close(relMap, RowExclusiveLock);
    1194             : 
    1195        6600 :     ReleaseSysCache(tup);
    1196             : 
    1197        6600 :     return address;
    1198             : }
    1199             : 
    1200             : /*
    1201             :  * Check whether a token type name is a member of a TSTokenTypeItem list.
    1202             :  */
    1203             : static bool
    1204       40756 : tstoken_list_member(char *token_name, List *tokens)
    1205             : {
    1206             :     ListCell   *c;
    1207       40756 :     bool        found = false;
    1208             : 
    1209      219772 :     foreach(c, tokens)
    1210             :     {
    1211      179040 :         TSTokenTypeItem *ts = (TSTokenTypeItem *) lfirst(c);
    1212             : 
    1213      179040 :         if (strcmp(token_name, ts->name) == 0)
    1214             :         {
    1215          24 :             found = true;
    1216          24 :             break;
    1217             :         }
    1218             :     }
    1219             : 
    1220       40756 :     return found;
    1221             : }
    1222             : 
    1223             : /*
    1224             :  * Translate a list of token type names to a list of unique TSTokenTypeItem.
    1225             :  *
    1226             :  * Duplicated entries list are removed from tokennames.
    1227             :  */
    1228             : static List *
    1229        6624 : getTokenTypes(Oid prsId, List *tokennames)
    1230             : {
    1231        6624 :     TSParserCacheEntry *prs = lookup_ts_parser_cache(prsId);
    1232             :     LexDescr   *list;
    1233        6624 :     List       *result = NIL;
    1234             :     int         ntoken;
    1235             :     ListCell   *tn;
    1236             : 
    1237        6624 :     ntoken = list_length(tokennames);
    1238        6624 :     if (ntoken == 0)
    1239          18 :         return NIL;
    1240             : 
    1241        6606 :     if (!OidIsValid(prs->lextypeOid))
    1242           0 :         elog(ERROR, "method lextype isn't defined for text search parser %u",
    1243             :              prsId);
    1244             : 
    1245             :     /* lextype takes one dummy argument */
    1246        6606 :     list = (LexDescr *) DatumGetPointer(OidFunctionCall1(prs->lextypeOid,
    1247             :                                                          (Datum) 0));
    1248             : 
    1249       47344 :     foreach(tn, tokennames)
    1250             :     {
    1251       40756 :         String     *val = lfirst_node(String, tn);
    1252       40756 :         bool        found = false;
    1253             :         int         j;
    1254             : 
    1255             :         /* Skip if this token is already in the result */
    1256       40756 :         if (tstoken_list_member(strVal(val), result))
    1257          24 :             continue;
    1258             : 
    1259       40732 :         j = 0;
    1260      458482 :         while (list && list[j].lexid)
    1261             :         {
    1262      458464 :             if (strcmp(strVal(val), list[j].alias) == 0)
    1263             :             {
    1264       40714 :                 TSTokenTypeItem *ts = (TSTokenTypeItem *) palloc0(sizeof(TSTokenTypeItem));
    1265             : 
    1266       40714 :                 ts->num = list[j].lexid;
    1267       40714 :                 ts->name = pstrdup(strVal(val));
    1268       40714 :                 result = lappend(result, ts);
    1269       40714 :                 found = true;
    1270       40714 :                 break;
    1271             :             }
    1272      417750 :             j++;
    1273             :         }
    1274       40732 :         if (!found)
    1275          18 :             ereport(ERROR,
    1276             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
    1277             :                      errmsg("token type \"%s\" does not exist",
    1278             :                             strVal(val))));
    1279             :     }
    1280             : 
    1281        6588 :     return result;
    1282             : }
    1283             : 
    1284             : /*
    1285             :  * ALTER TEXT SEARCH CONFIGURATION ADD/ALTER MAPPING
    1286             :  */
    1287             : static void
    1288        6594 : MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
    1289             :                          HeapTuple tup, Relation relMap)
    1290             : {
    1291             :     Form_pg_ts_config tsform;
    1292             :     Oid         cfgId;
    1293             :     ScanKeyData skey[2];
    1294             :     SysScanDesc scan;
    1295             :     HeapTuple   maptup;
    1296             :     int         i;
    1297             :     int         j;
    1298             :     Oid         prsId;
    1299        6594 :     List       *tokens = NIL;
    1300             :     int         ntoken;
    1301             :     Oid        *dictIds;
    1302             :     int         ndict;
    1303             :     ListCell   *c;
    1304             :     CatalogIndexState indstate;
    1305             : 
    1306        6594 :     tsform = (Form_pg_ts_config) GETSTRUCT(tup);
    1307        6594 :     cfgId = tsform->oid;
    1308        6594 :     prsId = tsform->cfgparser;
    1309             : 
    1310        6594 :     tokens = getTokenTypes(prsId, stmt->tokentype);
    1311        6588 :     ntoken = list_length(tokens);
    1312             : 
    1313        6588 :     if (stmt->override)
    1314             :     {
    1315             :         /*
    1316             :          * delete maps for tokens if they exist and command was ALTER
    1317             :          */
    1318         130 :         foreach(c, tokens)
    1319             :         {
    1320         104 :             TSTokenTypeItem *ts = (TSTokenTypeItem *) lfirst(c);
    1321             : 
    1322         104 :             ScanKeyInit(&skey[0],
    1323             :                         Anum_pg_ts_config_map_mapcfg,
    1324             :                         BTEqualStrategyNumber, F_OIDEQ,
    1325             :                         ObjectIdGetDatum(cfgId));
    1326         104 :             ScanKeyInit(&skey[1],
    1327             :                         Anum_pg_ts_config_map_maptokentype,
    1328             :                         BTEqualStrategyNumber, F_INT4EQ,
    1329             :                         Int32GetDatum(ts->num));
    1330             : 
    1331         104 :             scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
    1332             :                                       NULL, 2, skey);
    1333             : 
    1334         226 :             while (HeapTupleIsValid((maptup = systable_getnext(scan))))
    1335             :             {
    1336         122 :                 CatalogTupleDelete(relMap, &maptup->t_self);
    1337             :             }
    1338             : 
    1339         104 :             systable_endscan(scan);
    1340             :         }
    1341             :     }
    1342             : 
    1343             :     /*
    1344             :      * Convert list of dictionary names to array of dict OIDs
    1345             :      */
    1346        6588 :     ndict = list_length(stmt->dicts);
    1347        6588 :     dictIds = (Oid *) palloc(sizeof(Oid) * ndict);
    1348        6588 :     i = 0;
    1349       13272 :     foreach(c, stmt->dicts)
    1350             :     {
    1351        6684 :         List       *names = (List *) lfirst(c);
    1352             : 
    1353        6684 :         dictIds[i] = get_ts_dict_oid(names, false);
    1354        6684 :         i++;
    1355             :     }
    1356             : 
    1357        6588 :     indstate = CatalogOpenIndexes(relMap);
    1358             : 
    1359        6588 :     if (stmt->replace)
    1360             :     {
    1361             :         /*
    1362             :          * Replace a specific dictionary in existing entries
    1363             :          */
    1364          18 :         Oid         dictOld = dictIds[0],
    1365          18 :                     dictNew = dictIds[1];
    1366             : 
    1367          18 :         ScanKeyInit(&skey[0],
    1368             :                     Anum_pg_ts_config_map_mapcfg,
    1369             :                     BTEqualStrategyNumber, F_OIDEQ,
    1370             :                     ObjectIdGetDatum(cfgId));
    1371             : 
    1372          18 :         scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
    1373             :                                   NULL, 1, skey);
    1374             : 
    1375         522 :         while (HeapTupleIsValid((maptup = systable_getnext(scan))))
    1376             :         {
    1377         504 :             Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
    1378             : 
    1379             :             /*
    1380             :              * check if it's one of target token types
    1381             :              */
    1382         504 :             if (tokens)
    1383             :             {
    1384           0 :                 bool        tokmatch = false;
    1385             : 
    1386           0 :                 foreach(c, tokens)
    1387             :                 {
    1388           0 :                     TSTokenTypeItem *ts = (TSTokenTypeItem *) lfirst(c);
    1389             : 
    1390           0 :                     if (cfgmap->maptokentype == ts->num)
    1391             :                     {
    1392           0 :                         tokmatch = true;
    1393           0 :                         break;
    1394             :                     }
    1395             :                 }
    1396           0 :                 if (!tokmatch)
    1397           0 :                     continue;
    1398             :             }
    1399             : 
    1400             :             /*
    1401             :              * replace dictionary if match
    1402             :              */
    1403         504 :             if (cfgmap->mapdict == dictOld)
    1404             :             {
    1405             :                 Datum       repl_val[Natts_pg_ts_config_map];
    1406             :                 bool        repl_null[Natts_pg_ts_config_map];
    1407             :                 bool        repl_repl[Natts_pg_ts_config_map];
    1408             :                 HeapTuple   newtup;
    1409             : 
    1410         162 :                 memset(repl_val, 0, sizeof(repl_val));
    1411         162 :                 memset(repl_null, false, sizeof(repl_null));
    1412         162 :                 memset(repl_repl, false, sizeof(repl_repl));
    1413             : 
    1414         162 :                 repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew);
    1415         162 :                 repl_repl[Anum_pg_ts_config_map_mapdict - 1] = true;
    1416             : 
    1417         162 :                 newtup = heap_modify_tuple(maptup,
    1418             :                                            RelationGetDescr(relMap),
    1419             :                                            repl_val, repl_null, repl_repl);
    1420         162 :                 CatalogTupleUpdateWithInfo(relMap, &newtup->t_self, newtup, indstate);
    1421             :             }
    1422             :         }
    1423             : 
    1424          18 :         systable_endscan(scan);
    1425             :     }
    1426             :     else
    1427             :     {
    1428             :         TupleTableSlot **slot;
    1429        6570 :         int         slotCount = 0;
    1430             :         int         nslots;
    1431             : 
    1432             :         /* Allocate the slots to use and initialize them */
    1433        6570 :         nslots = Min(ntoken * ndict,
    1434             :                      MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_ts_config_map));
    1435        6570 :         slot = palloc(sizeof(TupleTableSlot *) * nslots);
    1436       47428 :         for (i = 0; i < nslots; i++)
    1437       40858 :             slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(relMap),
    1438             :                                                &TTSOpsHeapTuple);
    1439             : 
    1440             :         /*
    1441             :          * Insertion of new entries
    1442             :          */
    1443       47266 :         foreach(c, tokens)
    1444             :         {
    1445       40696 :             TSTokenTypeItem *ts = (TSTokenTypeItem *) lfirst(c);
    1446             : 
    1447       81554 :             for (j = 0; j < ndict; j++)
    1448             :             {
    1449       40858 :                 ExecClearTuple(slot[slotCount]);
    1450             : 
    1451       40858 :                 memset(slot[slotCount]->tts_isnull, false,
    1452       40858 :                        slot[slotCount]->tts_tupleDescriptor->natts * sizeof(bool));
    1453             : 
    1454       40858 :                 slot[slotCount]->tts_values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId);
    1455       40858 :                 slot[slotCount]->tts_values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(ts->num);
    1456       40858 :                 slot[slotCount]->tts_values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1);
    1457       40858 :                 slot[slotCount]->tts_values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]);
    1458             : 
    1459       40858 :                 ExecStoreVirtualTuple(slot[slotCount]);
    1460       40858 :                 slotCount++;
    1461             : 
    1462             :                 /* If slots are full, insert a batch of tuples */
    1463       40858 :                 if (slotCount == nslots)
    1464             :                 {
    1465        6570 :                     CatalogTuplesMultiInsertWithInfo(relMap, slot, slotCount,
    1466             :                                                      indstate);
    1467        6570 :                     slotCount = 0;
    1468             :                 }
    1469             :             }
    1470             :         }
    1471             : 
    1472             :         /* Insert any tuples left in the buffer */
    1473        6570 :         if (slotCount > 0)
    1474           0 :             CatalogTuplesMultiInsertWithInfo(relMap, slot, slotCount,
    1475             :                                              indstate);
    1476             : 
    1477       47428 :         for (i = 0; i < nslots; i++)
    1478       40858 :             ExecDropSingleTupleTableSlot(slot[i]);
    1479             :     }
    1480             : 
    1481             :     /* clean up */
    1482        6588 :     CatalogCloseIndexes(indstate);
    1483             : 
    1484        6588 :     EventTriggerCollectAlterTSConfig(stmt, cfgId, dictIds, ndict);
    1485        6588 : }
    1486             : 
    1487             : /*
    1488             :  * ALTER TEXT SEARCH CONFIGURATION DROP MAPPING
    1489             :  */
    1490             : static void
    1491          30 : DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
    1492             :                          HeapTuple tup, Relation relMap)
    1493             : {
    1494             :     Form_pg_ts_config tsform;
    1495             :     Oid         cfgId;
    1496             :     ScanKeyData skey[2];
    1497             :     SysScanDesc scan;
    1498             :     HeapTuple   maptup;
    1499             :     Oid         prsId;
    1500          30 :     List       *tokens = NIL;
    1501             :     ListCell   *c;
    1502             : 
    1503          30 :     tsform = (Form_pg_ts_config) GETSTRUCT(tup);
    1504          30 :     cfgId = tsform->oid;
    1505          30 :     prsId = tsform->cfgparser;
    1506             : 
    1507          30 :     tokens = getTokenTypes(prsId, stmt->tokentype);
    1508             : 
    1509          30 :     foreach(c, tokens)
    1510             :     {
    1511          18 :         TSTokenTypeItem *ts = (TSTokenTypeItem *) lfirst(c);
    1512          18 :         bool        found = false;
    1513             : 
    1514          18 :         ScanKeyInit(&skey[0],
    1515             :                     Anum_pg_ts_config_map_mapcfg,
    1516             :                     BTEqualStrategyNumber, F_OIDEQ,
    1517             :                     ObjectIdGetDatum(cfgId));
    1518          18 :         ScanKeyInit(&skey[1],
    1519             :                     Anum_pg_ts_config_map_maptokentype,
    1520             :                     BTEqualStrategyNumber, F_INT4EQ,
    1521             :                     Int32GetDatum(ts->num));
    1522             : 
    1523          18 :         scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
    1524             :                                   NULL, 2, skey);
    1525             : 
    1526          24 :         while (HeapTupleIsValid((maptup = systable_getnext(scan))))
    1527             :         {
    1528           6 :             CatalogTupleDelete(relMap, &maptup->t_self);
    1529           6 :             found = true;
    1530             :         }
    1531             : 
    1532          18 :         systable_endscan(scan);
    1533             : 
    1534          18 :         if (!found)
    1535             :         {
    1536          12 :             if (!stmt->missing_ok)
    1537             :             {
    1538           6 :                 ereport(ERROR,
    1539             :                         (errcode(ERRCODE_UNDEFINED_OBJECT),
    1540             :                          errmsg("mapping for token type \"%s\" does not exist",
    1541             :                                 ts->name)));
    1542             :             }
    1543             :             else
    1544             :             {
    1545           6 :                 ereport(NOTICE,
    1546             :                         (errmsg("mapping for token type \"%s\" does not exist, skipping",
    1547             :                                 ts->name)));
    1548             :             }
    1549             :         }
    1550             :     }
    1551             : 
    1552          12 :     EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0);
    1553          12 : }
    1554             : 
    1555             : 
    1556             : /*
    1557             :  * Serialize dictionary options, producing a TEXT datum from a List of DefElem
    1558             :  *
    1559             :  * This is used to form the value stored in pg_ts_dict.dictinitoption.
    1560             :  * For the convenience of pg_dump, the output is formatted exactly as it
    1561             :  * would need to appear in CREATE TEXT SEARCH DICTIONARY to reproduce the
    1562             :  * same options.
    1563             :  */
    1564             : text *
    1565        2240 : serialize_deflist(List *deflist)
    1566             : {
    1567             :     text       *result;
    1568             :     StringInfoData buf;
    1569             :     ListCell   *l;
    1570             : 
    1571        2240 :     initStringInfo(&buf);
    1572             : 
    1573        5766 :     foreach(l, deflist)
    1574             :     {
    1575        3526 :         DefElem    *defel = (DefElem *) lfirst(l);
    1576        3526 :         char       *val = defGetString(defel);
    1577             : 
    1578        3526 :         appendStringInfo(&buf, "%s = ",
    1579        3526 :                          quote_identifier(defel->defname));
    1580             : 
    1581             :         /*
    1582             :          * If the value is a T_Integer or T_Float, emit it without quotes,
    1583             :          * otherwise with quotes.  This is essential to allow correct
    1584             :          * reconstruction of the node type as well as the value.
    1585             :          */
    1586        3526 :         if (IsA(defel->arg, Integer) || IsA(defel->arg, Float))
    1587          14 :             appendStringInfoString(&buf, val);
    1588             :         else
    1589             :         {
    1590             :             /* If backslashes appear, force E syntax to quote them safely */
    1591        3512 :             if (strchr(val, '\\'))
    1592           0 :                 appendStringInfoChar(&buf, ESCAPE_STRING_SYNTAX);
    1593        3512 :             appendStringInfoChar(&buf, '\'');
    1594       29394 :             while (*val)
    1595             :             {
    1596       25882 :                 char        ch = *val++;
    1597             : 
    1598       25882 :                 if (SQL_STR_DOUBLE(ch, true))
    1599           0 :                     appendStringInfoChar(&buf, ch);
    1600       25882 :                 appendStringInfoChar(&buf, ch);
    1601             :             }
    1602        3512 :             appendStringInfoChar(&buf, '\'');
    1603             :         }
    1604        3526 :         if (lnext(deflist, l) != NULL)
    1605        1286 :             appendStringInfoString(&buf, ", ");
    1606             :     }
    1607             : 
    1608        2240 :     result = cstring_to_text_with_len(buf.data, buf.len);
    1609        2240 :     pfree(buf.data);
    1610        2240 :     return result;
    1611             : }
    1612             : 
    1613             : /*
    1614             :  * Deserialize dictionary options, reconstructing a List of DefElem from TEXT
    1615             :  *
    1616             :  * This is also used for prsheadline options, so for backward compatibility
    1617             :  * we need to accept a few things serialize_deflist() will never emit:
    1618             :  * in particular, unquoted and double-quoted strings.
    1619             :  */
    1620             : List *
    1621         262 : deserialize_deflist(Datum txt)
    1622             : {
    1623         262 :     text       *in = DatumGetTextPP(txt);   /* in case it's toasted */
    1624         262 :     List       *result = NIL;
    1625         262 :     int         len = VARSIZE_ANY_EXHDR(in);
    1626             :     char       *ptr,
    1627             :                *endptr,
    1628             :                *workspace,
    1629         262 :                *wsptr = NULL,
    1630         262 :                *startvalue = NULL;
    1631             :     typedef enum
    1632             :     {
    1633             :         CS_WAITKEY,
    1634             :         CS_INKEY,
    1635             :         CS_INQKEY,
    1636             :         CS_WAITEQ,
    1637             :         CS_WAITVALUE,
    1638             :         CS_INSQVALUE,
    1639             :         CS_INDQVALUE,
    1640             :         CS_INWVALUE
    1641             :     } ds_state;
    1642         262 :     ds_state    state = CS_WAITKEY;
    1643             : 
    1644         262 :     workspace = (char *) palloc(len + 1);   /* certainly enough room */
    1645         262 :     ptr = VARDATA_ANY(in);
    1646         262 :     endptr = ptr + len;
    1647       12098 :     for (; ptr < endptr; ptr++)
    1648             :     {
    1649       11836 :         switch (state)
    1650             :         {
    1651        1096 :             case CS_WAITKEY:
    1652        1096 :                 if (isspace((unsigned char) *ptr) || *ptr == ',')
    1653         528 :                     continue;
    1654         568 :                 if (*ptr == '"')
    1655             :                 {
    1656           0 :                     wsptr = workspace;
    1657           0 :                     state = CS_INQKEY;
    1658             :                 }
    1659             :                 else
    1660             :                 {
    1661         568 :                     wsptr = workspace;
    1662         568 :                     *wsptr++ = *ptr;
    1663         568 :                     state = CS_INKEY;
    1664             :                 }
    1665         568 :                 break;
    1666        5018 :             case CS_INKEY:
    1667        5018 :                 if (isspace((unsigned char) *ptr))
    1668             :                 {
    1669         454 :                     *wsptr++ = '\0';
    1670         454 :                     state = CS_WAITEQ;
    1671             :                 }
    1672        4564 :                 else if (*ptr == '=')
    1673             :                 {
    1674         114 :                     *wsptr++ = '\0';
    1675         114 :                     state = CS_WAITVALUE;
    1676             :                 }
    1677             :                 else
    1678             :                 {
    1679        4450 :                     *wsptr++ = *ptr;
    1680             :                 }
    1681        5018 :                 break;
    1682           0 :             case CS_INQKEY:
    1683           0 :                 if (*ptr == '"')
    1684             :                 {
    1685           0 :                     if (ptr + 1 < endptr && ptr[1] == '"')
    1686             :                     {
    1687             :                         /* copy only one of the two quotes */
    1688           0 :                         *wsptr++ = *ptr++;
    1689             :                     }
    1690             :                     else
    1691             :                     {
    1692           0 :                         *wsptr++ = '\0';
    1693           0 :                         state = CS_WAITEQ;
    1694             :                     }
    1695             :                 }
    1696             :                 else
    1697             :                 {
    1698           0 :                     *wsptr++ = *ptr;
    1699             :                 }
    1700           0 :                 break;
    1701         454 :             case CS_WAITEQ:
    1702         454 :                 if (*ptr == '=')
    1703         454 :                     state = CS_WAITVALUE;
    1704           0 :                 else if (!isspace((unsigned char) *ptr))
    1705           0 :                     ereport(ERROR,
    1706             :                             (errcode(ERRCODE_SYNTAX_ERROR),
    1707             :                              errmsg("invalid parameter list format: \"%s\"",
    1708             :                                     text_to_cstring(in))));
    1709         454 :                 break;
    1710        1022 :             case CS_WAITVALUE:
    1711        1022 :                 if (*ptr == '\'')
    1712             :                 {
    1713         376 :                     startvalue = wsptr;
    1714         376 :                     state = CS_INSQVALUE;
    1715             :                 }
    1716         646 :                 else if (*ptr == 'E' && ptr + 1 < endptr && ptr[1] == '\'')
    1717             :                 {
    1718           0 :                     ptr++;
    1719           0 :                     startvalue = wsptr;
    1720           0 :                     state = CS_INSQVALUE;
    1721             :                 }
    1722         646 :                 else if (*ptr == '"')
    1723             :                 {
    1724           0 :                     startvalue = wsptr;
    1725           0 :                     state = CS_INDQVALUE;
    1726             :                 }
    1727         646 :                 else if (!isspace((unsigned char) *ptr))
    1728             :                 {
    1729         192 :                     startvalue = wsptr;
    1730         192 :                     *wsptr++ = *ptr;
    1731         192 :                     state = CS_INWVALUE;
    1732             :                 }
    1733        1022 :                 break;
    1734        4070 :             case CS_INSQVALUE:
    1735        4070 :                 if (*ptr == '\'')
    1736             :                 {
    1737         376 :                     if (ptr + 1 < endptr && ptr[1] == '\'')
    1738             :                     {
    1739             :                         /* copy only one of the two quotes */
    1740           0 :                         *wsptr++ = *ptr++;
    1741             :                     }
    1742             :                     else
    1743             :                     {
    1744         376 :                         *wsptr++ = '\0';
    1745         376 :                         result = lappend(result,
    1746         376 :                                          buildDefItem(workspace,
    1747             :                                                       startvalue,
    1748             :                                                       true));
    1749         376 :                         state = CS_WAITKEY;
    1750             :                     }
    1751             :                 }
    1752        3694 :                 else if (*ptr == '\\')
    1753             :                 {
    1754           0 :                     if (ptr + 1 < endptr && ptr[1] == '\\')
    1755             :                     {
    1756             :                         /* copy only one of the two backslashes */
    1757           0 :                         *wsptr++ = *ptr++;
    1758             :                     }
    1759             :                     else
    1760           0 :                         *wsptr++ = *ptr;
    1761             :                 }
    1762             :                 else
    1763             :                 {
    1764        3694 :                     *wsptr++ = *ptr;
    1765             :                 }
    1766        4070 :                 break;
    1767           0 :             case CS_INDQVALUE:
    1768           0 :                 if (*ptr == '"')
    1769             :                 {
    1770           0 :                     if (ptr + 1 < endptr && ptr[1] == '"')
    1771             :                     {
    1772             :                         /* copy only one of the two quotes */
    1773           0 :                         *wsptr++ = *ptr++;
    1774             :                     }
    1775             :                     else
    1776             :                     {
    1777           0 :                         *wsptr++ = '\0';
    1778           0 :                         result = lappend(result,
    1779           0 :                                          buildDefItem(workspace,
    1780             :                                                       startvalue,
    1781             :                                                       true));
    1782           0 :                         state = CS_WAITKEY;
    1783             :                     }
    1784             :                 }
    1785             :                 else
    1786             :                 {
    1787           0 :                     *wsptr++ = *ptr;
    1788             :                 }
    1789           0 :                 break;
    1790         176 :             case CS_INWVALUE:
    1791         176 :                 if (*ptr == ',' || isspace((unsigned char) *ptr))
    1792             :                 {
    1793          78 :                     *wsptr++ = '\0';
    1794          78 :                     result = lappend(result,
    1795          78 :                                      buildDefItem(workspace,
    1796             :                                                   startvalue,
    1797             :                                                   false));
    1798          78 :                     state = CS_WAITKEY;
    1799             :                 }
    1800             :                 else
    1801             :                 {
    1802          98 :                     *wsptr++ = *ptr;
    1803             :                 }
    1804         176 :                 break;
    1805           0 :             default:
    1806           0 :                 elog(ERROR, "unrecognized deserialize_deflist state: %d",
    1807             :                      state);
    1808             :         }
    1809             :     }
    1810             : 
    1811         262 :     if (state == CS_INWVALUE)
    1812             :     {
    1813         114 :         *wsptr++ = '\0';
    1814         114 :         result = lappend(result,
    1815         114 :                          buildDefItem(workspace,
    1816             :                                       startvalue,
    1817             :                                       false));
    1818             :     }
    1819         148 :     else if (state != CS_WAITKEY)
    1820           0 :         ereport(ERROR,
    1821             :                 (errcode(ERRCODE_SYNTAX_ERROR),
    1822             :                  errmsg("invalid parameter list format: \"%s\"",
    1823             :                         text_to_cstring(in))));
    1824             : 
    1825         262 :     pfree(workspace);
    1826             : 
    1827         262 :     return result;
    1828             : }
    1829             : 
    1830             : /*
    1831             :  * Build one DefElem for deserialize_deflist
    1832             :  */
    1833             : static DefElem *
    1834         568 : buildDefItem(const char *name, const char *val, bool was_quoted)
    1835             : {
    1836             :     /* If input was quoted, always emit as string */
    1837         568 :     if (!was_quoted && val[0] != '\0')
    1838             :     {
    1839             :         int         v;
    1840             :         char       *endptr;
    1841             : 
    1842             :         /* Try to parse as an integer */
    1843         192 :         errno = 0;
    1844         192 :         v = strtoint(val, &endptr, 10);
    1845         192 :         if (errno == 0 && *endptr == '\0')
    1846         138 :             return makeDefElem(pstrdup(name),
    1847         122 :                                (Node *) makeInteger(v),
    1848             :                                -1);
    1849             :         /* Nope, how about as a float? */
    1850          70 :         errno = 0;
    1851          70 :         (void) strtod(val, &endptr);
    1852          70 :         if (errno == 0 && *endptr == '\0')
    1853          10 :             return makeDefElem(pstrdup(name),
    1854          10 :                                (Node *) makeFloat(pstrdup(val)),
    1855             :                                -1);
    1856             : 
    1857          60 :         if (strcmp(val, "true") == 0)
    1858           6 :             return makeDefElem(pstrdup(name),
    1859           6 :                                (Node *) makeBoolean(true),
    1860             :                                -1);
    1861          54 :         if (strcmp(val, "false") == 0)
    1862           0 :             return makeDefElem(pstrdup(name),
    1863           0 :                                (Node *) makeBoolean(false),
    1864             :                                -1);
    1865             :     }
    1866             :     /* Just make it a string */
    1867         430 :     return makeDefElem(pstrdup(name),
    1868         430 :                        (Node *) makeString(pstrdup(val)),
    1869             :                        -1);
    1870             : }

Generated by: LCOV version 1.14