LCOV - code coverage report
Current view: top level - src/backend/commands - tsearchcmds.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 87.9 % 704 619
Test Date: 2026-02-28 06:15:02 Functions: 100.0 % 22 22
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-2026, 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           69 : get_ts_parser_func(DefElem *defel, int attnum)
      75              : {
      76           69 :     List       *funcName = defGetQualifiedName(defel);
      77              :     Oid         typeId[3];
      78              :     Oid         retTypeId;
      79              :     int         nargs;
      80              :     Oid         procOid;
      81              : 
      82           69 :     retTypeId = INTERNALOID;    /* correct for most */
      83           69 :     typeId[0] = INTERNALOID;
      84           69 :     switch (attnum)
      85              :     {
      86           17 :         case Anum_pg_ts_parser_prsstart:
      87           17 :             nargs = 2;
      88           17 :             typeId[1] = INT4OID;
      89           17 :             break;
      90           17 :         case Anum_pg_ts_parser_prstoken:
      91           17 :             nargs = 3;
      92           17 :             typeId[1] = INTERNALOID;
      93           17 :             typeId[2] = INTERNALOID;
      94           17 :             break;
      95           17 :         case Anum_pg_ts_parser_prsend:
      96           17 :             nargs = 1;
      97           17 :             retTypeId = VOIDOID;
      98           17 :             break;
      99            1 :         case Anum_pg_ts_parser_prsheadline:
     100            1 :             nargs = 3;
     101            1 :             typeId[1] = INTERNALOID;
     102            1 :             typeId[2] = TSQUERYOID;
     103            1 :             break;
     104           17 :         case Anum_pg_ts_parser_prslextype:
     105           17 :             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           17 :             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           69 :     procOid = LookupFuncName(funcName, nargs, typeId, false);
     121           69 :     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           69 :     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           17 : makeParserDependencies(HeapTuple tuple)
     138              : {
     139           17 :     Form_pg_ts_parser prs = (Form_pg_ts_parser) GETSTRUCT(tuple);
     140              :     ObjectAddress myself,
     141              :                 referenced;
     142              :     ObjectAddresses *addrs;
     143              : 
     144           17 :     ObjectAddressSet(myself, TSParserRelationId, prs->oid);
     145              : 
     146              :     /* dependency on extension */
     147           17 :     recordDependencyOnCurrentExtension(&myself, false);
     148              : 
     149           17 :     addrs = new_object_addresses();
     150              : 
     151              :     /* dependency on namespace */
     152           17 :     ObjectAddressSet(referenced, NamespaceRelationId, prs->prsnamespace);
     153           17 :     add_exact_object_address(&referenced, addrs);
     154              : 
     155              :     /* dependencies on functions */
     156           17 :     ObjectAddressSet(referenced, ProcedureRelationId, prs->prsstart);
     157           17 :     add_exact_object_address(&referenced, addrs);
     158              : 
     159           17 :     referenced.objectId = prs->prstoken;
     160           17 :     add_exact_object_address(&referenced, addrs);
     161              : 
     162           17 :     referenced.objectId = prs->prsend;
     163           17 :     add_exact_object_address(&referenced, addrs);
     164              : 
     165           17 :     referenced.objectId = prs->prslextype;
     166           17 :     add_exact_object_address(&referenced, addrs);
     167              : 
     168           17 :     if (OidIsValid(prs->prsheadline))
     169              :     {
     170            1 :         referenced.objectId = prs->prsheadline;
     171            1 :         add_exact_object_address(&referenced, addrs);
     172              :     }
     173              : 
     174           17 :     record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
     175           17 :     free_object_addresses(addrs);
     176              : 
     177           17 :     return myself;
     178              : }
     179              : 
     180              : /*
     181              :  * CREATE TEXT SEARCH PARSER
     182              :  */
     183              : ObjectAddress
     184           20 : 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           20 :     if (!superuser())
     198            0 :         ereport(ERROR,
     199              :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     200              :                  errmsg("must be superuser to create text search parsers")));
     201              : 
     202           20 :     prsRel = table_open(TSParserRelationId, RowExclusiveLock);
     203              : 
     204              :     /* Convert list of names to a name and namespace */
     205           20 :     namespaceoid = QualifiedNameGetCreationNamespace(names, &prsname);
     206              : 
     207              :     /* initialize tuple fields with name/namespace */
     208           20 :     memset(values, 0, sizeof(values));
     209           20 :     memset(nulls, false, sizeof(nulls));
     210              : 
     211           20 :     prsOid = GetNewOidWithIndex(prsRel, TSParserOidIndexId,
     212              :                                 Anum_pg_ts_parser_oid);
     213           20 :     values[Anum_pg_ts_parser_oid - 1] = ObjectIdGetDatum(prsOid);
     214           20 :     namestrcpy(&pname, prsname);
     215           20 :     values[Anum_pg_ts_parser_prsname - 1] = NameGetDatum(&pname);
     216           20 :     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           89 :     foreach(pl, parameters)
     222              :     {
     223           72 :         DefElem    *defel = (DefElem *) lfirst(pl);
     224              : 
     225           72 :         if (strcmp(defel->defname, "start") == 0)
     226              :         {
     227           17 :             values[Anum_pg_ts_parser_prsstart - 1] =
     228           17 :                 get_ts_parser_func(defel, Anum_pg_ts_parser_prsstart);
     229              :         }
     230           55 :         else if (strcmp(defel->defname, "gettoken") == 0)
     231              :         {
     232           17 :             values[Anum_pg_ts_parser_prstoken - 1] =
     233           17 :                 get_ts_parser_func(defel, Anum_pg_ts_parser_prstoken);
     234              :         }
     235           38 :         else if (strcmp(defel->defname, "end") == 0)
     236              :         {
     237           17 :             values[Anum_pg_ts_parser_prsend - 1] =
     238           17 :                 get_ts_parser_func(defel, Anum_pg_ts_parser_prsend);
     239              :         }
     240           21 :         else if (strcmp(defel->defname, "headline") == 0)
     241              :         {
     242            1 :             values[Anum_pg_ts_parser_prsheadline - 1] =
     243            1 :                 get_ts_parser_func(defel, Anum_pg_ts_parser_prsheadline);
     244              :         }
     245           20 :         else if (strcmp(defel->defname, "lextypes") == 0)
     246              :         {
     247           17 :             values[Anum_pg_ts_parser_prslextype - 1] =
     248           17 :                 get_ts_parser_func(defel, Anum_pg_ts_parser_prslextype);
     249              :         }
     250              :         else
     251            3 :             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           17 :     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           17 :     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           17 :     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           17 :     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           17 :     tup = heap_form_tuple(prsRel->rd_att, values, nulls);
     284              : 
     285           17 :     CatalogTupleInsert(prsRel, tup);
     286              : 
     287           17 :     address = makeParserDependencies(tup);
     288              : 
     289              :     /* Post creation hook for new text search parser */
     290           17 :     InvokeObjectPostCreateHook(TSParserRelationId, prsOid, 0);
     291              : 
     292           17 :     heap_freetuple(tup);
     293              : 
     294           17 :     table_close(prsRel, RowExclusiveLock);
     295              : 
     296           17 :     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         1580 : makeDictionaryDependencies(HeapTuple tuple)
     308              : {
     309         1580 :     Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tuple);
     310              :     ObjectAddress myself,
     311              :                 referenced;
     312              :     ObjectAddresses *addrs;
     313              : 
     314         1580 :     ObjectAddressSet(myself, TSDictionaryRelationId, dict->oid);
     315              : 
     316              :     /* dependency on owner */
     317         1580 :     recordDependencyOnOwner(myself.classId, myself.objectId, dict->dictowner);
     318              : 
     319              :     /* dependency on extension */
     320         1580 :     recordDependencyOnCurrentExtension(&myself, false);
     321              : 
     322         1580 :     addrs = new_object_addresses();
     323              : 
     324              :     /* dependency on namespace */
     325         1580 :     ObjectAddressSet(referenced, NamespaceRelationId, dict->dictnamespace);
     326         1580 :     add_exact_object_address(&referenced, addrs);
     327              : 
     328              :     /* dependency on template */
     329         1580 :     ObjectAddressSet(referenced, TSTemplateRelationId, dict->dicttemplate);
     330         1580 :     add_exact_object_address(&referenced, addrs);
     331              : 
     332         1580 :     record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
     333         1580 :     free_object_addresses(addrs);
     334              : 
     335         1580 :     return myself;
     336              : }
     337              : 
     338              : /*
     339              :  * verify that a template's init method accepts a proposed option list
     340              :  */
     341              : static void
     342         1612 : 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         1612 :     if (!IsUnderPostmaster)
     356         1519 :         return;
     357              : 
     358           93 :     tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId));
     359           93 :     if (!HeapTupleIsValid(tup)) /* should not happen */
     360            0 :         elog(ERROR, "cache lookup failed for text search template %u",
     361              :              tmplId);
     362           93 :     tform = (Form_pg_ts_template) GETSTRUCT(tup);
     363              : 
     364           93 :     initmethod = tform->tmplinit;
     365              : 
     366           93 :     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           93 :         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           93 :         (void) OidFunctionCall1(initmethod, PointerGetDatum(dictoptions));
     388              :     }
     389              : 
     390           77 :     ReleaseSysCache(tup);
     391              : }
     392              : 
     393              : /*
     394              :  * CREATE TEXT SEARCH DICTIONARY
     395              :  */
     396              : ObjectAddress
     397         1592 : 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         1592 :     Oid         templId = InvalidOid;
     406         1592 :     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         1592 :     namespaceoid = QualifiedNameGetCreationNamespace(names, &dictname);
     415              : 
     416              :     /* Check we have creation rights in target namespace */
     417         1592 :     aclresult = object_aclcheck(NamespaceRelationId, namespaceoid, GetUserId(), ACL_CREATE);
     418         1592 :     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         5538 :     foreach(pl, parameters)
     426              :     {
     427         3946 :         DefElem    *defel = (DefElem *) lfirst(pl);
     428              : 
     429         3946 :         if (strcmp(defel->defname, "template") == 0)
     430              :         {
     431         1592 :             templId = get_ts_template_oid(defGetQualifiedName(defel), false);
     432              :         }
     433              :         else
     434              :         {
     435              :             /* Assume it's an option for the dictionary itself */
     436         2354 :             dictoptions = lappend(dictoptions, defel);
     437              :         }
     438              :     }
     439              : 
     440              :     /*
     441              :      * Validation
     442              :      */
     443         1592 :     if (!OidIsValid(templId))
     444            0 :         ereport(ERROR,
     445              :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     446              :                  errmsg("text search template is required")));
     447              : 
     448         1592 :     verify_dictoptions(templId, dictoptions);
     449              : 
     450              : 
     451         1580 :     dictRel = table_open(TSDictionaryRelationId, RowExclusiveLock);
     452              : 
     453              :     /*
     454              :      * Looks good, insert
     455              :      */
     456         1580 :     memset(values, 0, sizeof(values));
     457         1580 :     memset(nulls, false, sizeof(nulls));
     458              : 
     459         1580 :     dictOid = GetNewOidWithIndex(dictRel, TSDictionaryOidIndexId,
     460              :                                  Anum_pg_ts_dict_oid);
     461         1580 :     values[Anum_pg_ts_dict_oid - 1] = ObjectIdGetDatum(dictOid);
     462         1580 :     namestrcpy(&dname, dictname);
     463         1580 :     values[Anum_pg_ts_dict_dictname - 1] = NameGetDatum(&dname);
     464         1580 :     values[Anum_pg_ts_dict_dictnamespace - 1] = ObjectIdGetDatum(namespaceoid);
     465         1580 :     values[Anum_pg_ts_dict_dictowner - 1] = ObjectIdGetDatum(GetUserId());
     466         1580 :     values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(templId);
     467         1580 :     if (dictoptions)
     468         1559 :         values[Anum_pg_ts_dict_dictinitoption - 1] =
     469         1559 :             PointerGetDatum(serialize_deflist(dictoptions));
     470              :     else
     471           21 :         nulls[Anum_pg_ts_dict_dictinitoption - 1] = true;
     472              : 
     473         1580 :     tup = heap_form_tuple(dictRel->rd_att, values, nulls);
     474              : 
     475         1580 :     CatalogTupleInsert(dictRel, tup);
     476              : 
     477         1580 :     address = makeDictionaryDependencies(tup);
     478              : 
     479              :     /* Post creation hook for new text search dictionary */
     480         1580 :     InvokeObjectPostCreateHook(TSDictionaryRelationId, dictOid, 0);
     481              : 
     482         1580 :     heap_freetuple(tup);
     483              : 
     484         1580 :     table_close(dictRel, RowExclusiveLock);
     485              : 
     486         1580 :     return address;
     487              : }
     488              : 
     489              : /*
     490              :  * ALTER TEXT SEARCH DICTIONARY
     491              :  */
     492              : ObjectAddress
     493           20 : 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           20 :     dictId = get_ts_dict_oid(stmt->dictname, false);
     509              : 
     510           20 :     rel = table_open(TSDictionaryRelationId, RowExclusiveLock);
     511              : 
     512           20 :     tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
     513              : 
     514           20 :     if (!HeapTupleIsValid(tup))
     515            0 :         elog(ERROR, "cache lookup failed for text search dictionary %u",
     516              :              dictId);
     517              : 
     518              :     /* must be owner */
     519           20 :     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           20 :     opt = SysCacheGetAttr(TSDICTOID, tup,
     525              :                           Anum_pg_ts_dict_dictinitoption,
     526              :                           &isnull);
     527           20 :     if (isnull)
     528            3 :         dictoptions = NIL;
     529              :     else
     530           17 :         dictoptions = deserialize_deflist(opt);
     531              : 
     532              :     /*
     533              :      * Modify the options list as per specified changes
     534              :      */
     535           68 :     foreach(pl, stmt->options)
     536              :     {
     537           48 :         DefElem    *defel = (DefElem *) lfirst(pl);
     538              :         ListCell   *cell;
     539              : 
     540              :         /*
     541              :          * Remove any matches ...
     542              :          */
     543          226 :         foreach(cell, dictoptions)
     544              :         {
     545          178 :             DefElem    *oldel = (DefElem *) lfirst(cell);
     546              : 
     547          178 :             if (strcmp(oldel->defname, defel->defname) == 0)
     548           36 :                 dictoptions = foreach_delete_current(dictoptions, cell);
     549              :         }
     550              : 
     551              :         /*
     552              :          * and add new value if it's got one
     553              :          */
     554           48 :         if (defel->arg)
     555           48 :             dictoptions = lappend(dictoptions, defel);
     556              :     }
     557              : 
     558              :     /*
     559              :      * Validate
     560              :      */
     561           20 :     verify_dictoptions(((Form_pg_ts_dict) GETSTRUCT(tup))->dicttemplate,
     562              :                        dictoptions);
     563              : 
     564              :     /*
     565              :      * Looks good, update
     566              :      */
     567           16 :     memset(repl_val, 0, sizeof(repl_val));
     568           16 :     memset(repl_null, false, sizeof(repl_null));
     569           16 :     memset(repl_repl, false, sizeof(repl_repl));
     570              : 
     571           16 :     if (dictoptions)
     572           16 :         repl_val[Anum_pg_ts_dict_dictinitoption - 1] =
     573           16 :             PointerGetDatum(serialize_deflist(dictoptions));
     574              :     else
     575            0 :         repl_null[Anum_pg_ts_dict_dictinitoption - 1] = true;
     576           16 :     repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = true;
     577              : 
     578           16 :     newtup = heap_modify_tuple(tup, RelationGetDescr(rel),
     579              :                                repl_val, repl_null, repl_repl);
     580              : 
     581           16 :     CatalogTupleUpdate(rel, &newtup->t_self, newtup);
     582              : 
     583           16 :     InvokeObjectPostAlterHook(TSDictionaryRelationId, dictId, 0);
     584              : 
     585           16 :     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           16 :     heap_freetuple(newtup);
     594           16 :     ReleaseSysCache(tup);
     595              : 
     596           16 :     table_close(rel, RowExclusiveLock);
     597              : 
     598           16 :     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          123 : get_ts_template_func(DefElem *defel, int attnum)
     610              : {
     611          123 :     List       *funcName = defGetQualifiedName(defel);
     612              :     Oid         typeId[4];
     613              :     Oid         retTypeId;
     614              :     int         nargs;
     615              :     Oid         procOid;
     616              : 
     617          123 :     retTypeId = INTERNALOID;
     618          123 :     typeId[0] = INTERNALOID;
     619          123 :     typeId[1] = INTERNALOID;
     620          123 :     typeId[2] = INTERNALOID;
     621          123 :     typeId[3] = INTERNALOID;
     622          123 :     switch (attnum)
     623              :     {
     624           55 :         case Anum_pg_ts_template_tmplinit:
     625           55 :             nargs = 1;
     626           55 :             break;
     627           68 :         case Anum_pg_ts_template_tmpllexize:
     628           68 :             nargs = 4;
     629           68 :             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          123 :     procOid = LookupFuncName(funcName, nargs, typeId, false);
     638          123 :     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          123 :     return ObjectIdGetDatum(procOid);
     646              : }
     647              : 
     648              : /*
     649              :  * make pg_depend entries for a new pg_ts_template entry
     650              :  */
     651              : static ObjectAddress
     652           68 : makeTSTemplateDependencies(HeapTuple tuple)
     653              : {
     654           68 :     Form_pg_ts_template tmpl = (Form_pg_ts_template) GETSTRUCT(tuple);
     655              :     ObjectAddress myself,
     656              :                 referenced;
     657              :     ObjectAddresses *addrs;
     658              : 
     659           68 :     ObjectAddressSet(myself, TSTemplateRelationId, tmpl->oid);
     660              : 
     661              :     /* dependency on extension */
     662           68 :     recordDependencyOnCurrentExtension(&myself, false);
     663              : 
     664           68 :     addrs = new_object_addresses();
     665              : 
     666              :     /* dependency on namespace */
     667           68 :     ObjectAddressSet(referenced, NamespaceRelationId, tmpl->tmplnamespace);
     668           68 :     add_exact_object_address(&referenced, addrs);
     669              : 
     670              :     /* dependencies on functions */
     671           68 :     ObjectAddressSet(referenced, ProcedureRelationId, tmpl->tmpllexize);
     672           68 :     add_exact_object_address(&referenced, addrs);
     673              : 
     674           68 :     if (OidIsValid(tmpl->tmplinit))
     675              :     {
     676           55 :         referenced.objectId = tmpl->tmplinit;
     677           55 :         add_exact_object_address(&referenced, addrs);
     678              :     }
     679              : 
     680           68 :     record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
     681           68 :     free_object_addresses(addrs);
     682              : 
     683           68 :     return myself;
     684              : }
     685              : 
     686              : /*
     687              :  * CREATE TEXT SEARCH TEMPLATE
     688              :  */
     689              : ObjectAddress
     690           71 : 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           71 :     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           71 :     namespaceoid = QualifiedNameGetCreationNamespace(names, &tmplname);
     711              : 
     712           71 :     tmplRel = table_open(TSTemplateRelationId, RowExclusiveLock);
     713              : 
     714          426 :     for (i = 0; i < Natts_pg_ts_template; i++)
     715              :     {
     716          355 :         nulls[i] = false;
     717          355 :         values[i] = ObjectIdGetDatum(InvalidOid);
     718              :     }
     719              : 
     720           71 :     tmplOid = GetNewOidWithIndex(tmplRel, TSTemplateOidIndexId,
     721              :                                  Anum_pg_ts_dict_oid);
     722           71 :     values[Anum_pg_ts_template_oid - 1] = ObjectIdGetDatum(tmplOid);
     723           71 :     namestrcpy(&dname, tmplname);
     724           71 :     values[Anum_pg_ts_template_tmplname - 1] = NameGetDatum(&dname);
     725           71 :     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          194 :     foreach(pl, parameters)
     731              :     {
     732          126 :         DefElem    *defel = (DefElem *) lfirst(pl);
     733              : 
     734          126 :         if (strcmp(defel->defname, "init") == 0)
     735              :         {
     736           55 :             values[Anum_pg_ts_template_tmplinit - 1] =
     737           55 :                 get_ts_template_func(defel, Anum_pg_ts_template_tmplinit);
     738           55 :             nulls[Anum_pg_ts_template_tmplinit - 1] = false;
     739              :         }
     740           71 :         else if (strcmp(defel->defname, "lexize") == 0)
     741              :         {
     742           68 :             values[Anum_pg_ts_template_tmpllexize - 1] =
     743           68 :                 get_ts_template_func(defel, Anum_pg_ts_template_tmpllexize);
     744           68 :             nulls[Anum_pg_ts_template_tmpllexize - 1] = false;
     745              :         }
     746              :         else
     747            3 :             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           68 :     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           68 :     tup = heap_form_tuple(tmplRel->rd_att, values, nulls);
     765              : 
     766           68 :     CatalogTupleInsert(tmplRel, tup);
     767              : 
     768           68 :     address = makeTSTemplateDependencies(tup);
     769              : 
     770              :     /* Post creation hook for new text search template */
     771           68 :     InvokeObjectPostCreateHook(TSTemplateRelationId, tmplOid, 0);
     772              : 
     773           68 :     heap_freetuple(tup);
     774              : 
     775           68 :     table_close(tmplRel, RowExclusiveLock);
     776              : 
     777           68 :     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         4677 : GetTSConfigTuple(List *names)
     788              : {
     789              :     HeapTuple   tup;
     790              :     Oid         cfgId;
     791              : 
     792         4677 :     cfgId = get_ts_config_oid(names, true);
     793         4677 :     if (!OidIsValid(cfgId))
     794            0 :         return NULL;
     795              : 
     796         4677 :     tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
     797              : 
     798         4677 :     if (!HeapTupleIsValid(tup)) /* should not happen */
     799            0 :         elog(ERROR, "cache lookup failed for text search configuration %u",
     800              :              cfgId);
     801              : 
     802         4677 :     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         6228 : makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
     813              :                               Relation mapRel)
     814              : {
     815         6228 :     Form_pg_ts_config cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
     816              :     ObjectAddresses *addrs;
     817              :     ObjectAddress myself,
     818              :                 referenced;
     819              : 
     820         6228 :     myself.classId = TSConfigRelationId;
     821         6228 :     myself.objectId = cfg->oid;
     822         6228 :     myself.objectSubId = 0;
     823              : 
     824              :     /* for ALTER case, first flush old dependencies, except extension deps */
     825         6228 :     if (removeOld)
     826              :     {
     827         4665 :         deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
     828         4665 :         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         6228 :     addrs = new_object_addresses();
     837              : 
     838              :     /* dependency on namespace */
     839         6228 :     referenced.classId = NamespaceRelationId;
     840         6228 :     referenced.objectId = cfg->cfgnamespace;
     841         6228 :     referenced.objectSubId = 0;
     842         6228 :     add_exact_object_address(&referenced, addrs);
     843              : 
     844              :     /* dependency on owner */
     845         6228 :     recordDependencyOnOwner(myself.classId, myself.objectId, cfg->cfgowner);
     846              : 
     847              :     /* dependency on extension */
     848         6228 :     recordDependencyOnCurrentExtension(&myself, removeOld);
     849              : 
     850              :     /* dependency on parser */
     851         6228 :     referenced.classId = TSParserRelationId;
     852         6228 :     referenced.objectId = cfg->cfgparser;
     853         6228 :     referenced.objectSubId = 0;
     854         6228 :     add_exact_object_address(&referenced, addrs);
     855              : 
     856              :     /* dependencies on dictionaries listed in config map */
     857         6228 :     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         4701 :         CommandCounterIncrement();
     865              : 
     866         4701 :         ScanKeyInit(&skey,
     867              :                     Anum_pg_ts_config_map_mapcfg,
     868              :                     BTEqualStrategyNumber, F_OIDEQ,
     869              :                     ObjectIdGetDatum(myself.objectId));
     870              : 
     871         4701 :         scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
     872              :                                   NULL, 1, &skey);
     873              : 
     874        80127 :         while (HeapTupleIsValid((maptup = systable_getnext(scan))))
     875              :         {
     876        75426 :             Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
     877              : 
     878        75426 :             referenced.classId = TSDictionaryRelationId;
     879        75426 :             referenced.objectId = cfgmap->mapdict;
     880        75426 :             referenced.objectSubId = 0;
     881        75426 :             add_exact_object_address(&referenced, addrs);
     882              :         }
     883              : 
     884         4701 :         systable_endscan(scan);
     885              :     }
     886              : 
     887              :     /* Record 'em (this includes duplicate elimination) */
     888         6228 :     record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
     889              : 
     890         6228 :     free_object_addresses(addrs);
     891              : 
     892         6228 :     return myself;
     893              : }
     894              : 
     895              : /*
     896              :  * CREATE TEXT SEARCH CONFIGURATION
     897              :  */
     898              : ObjectAddress
     899         1563 : DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied)
     900              : {
     901              :     Relation    cfgRel;
     902         1563 :     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         1563 :     Oid         sourceOid = InvalidOid;
     911         1563 :     Oid         prsOid = InvalidOid;
     912              :     Oid         cfgOid;
     913              :     ListCell   *pl;
     914              :     ObjectAddress address;
     915              : 
     916              :     /* Convert list of names to a name and namespace */
     917         1563 :     namespaceoid = QualifiedNameGetCreationNamespace(names, &cfgname);
     918              : 
     919              :     /* Check we have creation rights in target namespace */
     920         1563 :     aclresult = object_aclcheck(NamespaceRelationId, namespaceoid, GetUserId(), ACL_CREATE);
     921         1563 :     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         3126 :     foreach(pl, parameters)
     929              :     {
     930         1563 :         DefElem    *defel = (DefElem *) lfirst(pl);
     931              : 
     932         1563 :         if (strcmp(defel->defname, "parser") == 0)
     933         1527 :             prsOid = get_ts_parser_oid(defGetQualifiedName(defel), false);
     934           36 :         else if (strcmp(defel->defname, "copy") == 0)
     935           36 :             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         1563 :     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         1563 :     if (copied && OidIsValid(sourceOid))
     950              :     {
     951           36 :         ObjectAddressSet(*copied,
     952              :                          TSConfigRelationId,
     953              :                          sourceOid);
     954              :     }
     955              : 
     956              :     /*
     957              :      * Look up source config if given.
     958              :      */
     959         1563 :     if (OidIsValid(sourceOid))
     960              :     {
     961              :         Form_pg_ts_config cfg;
     962              : 
     963           36 :         tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(sourceOid));
     964           36 :         if (!HeapTupleIsValid(tup))
     965            0 :             elog(ERROR, "cache lookup failed for text search configuration %u",
     966              :                  sourceOid);
     967              : 
     968           36 :         cfg = (Form_pg_ts_config) GETSTRUCT(tup);
     969              : 
     970              :         /* use source's parser */
     971           36 :         prsOid = cfg->cfgparser;
     972              : 
     973           36 :         ReleaseSysCache(tup);
     974              :     }
     975              : 
     976              :     /*
     977              :      * Validation
     978              :      */
     979         1563 :     if (!OidIsValid(prsOid))
     980            0 :         ereport(ERROR,
     981              :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     982              :                  errmsg("text search parser is required")));
     983              : 
     984         1563 :     cfgRel = table_open(TSConfigRelationId, RowExclusiveLock);
     985              : 
     986              :     /*
     987              :      * Looks good, build tuple and insert
     988              :      */
     989         1563 :     memset(values, 0, sizeof(values));
     990         1563 :     memset(nulls, false, sizeof(nulls));
     991              : 
     992         1563 :     cfgOid = GetNewOidWithIndex(cfgRel, TSConfigOidIndexId,
     993              :                                 Anum_pg_ts_config_oid);
     994         1563 :     values[Anum_pg_ts_config_oid - 1] = ObjectIdGetDatum(cfgOid);
     995         1563 :     namestrcpy(&cname, cfgname);
     996         1563 :     values[Anum_pg_ts_config_cfgname - 1] = NameGetDatum(&cname);
     997         1563 :     values[Anum_pg_ts_config_cfgnamespace - 1] = ObjectIdGetDatum(namespaceoid);
     998         1563 :     values[Anum_pg_ts_config_cfgowner - 1] = ObjectIdGetDatum(GetUserId());
     999         1563 :     values[Anum_pg_ts_config_cfgparser - 1] = ObjectIdGetDatum(prsOid);
    1000              : 
    1001         1563 :     tup = heap_form_tuple(cfgRel->rd_att, values, nulls);
    1002              : 
    1003         1563 :     CatalogTupleInsert(cfgRel, tup);
    1004              : 
    1005         1563 :     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           36 :         mapRel = table_open(TSConfigMapRelationId, RowExclusiveLock);
    1021           36 :         mapDesc = RelationGetDescr(mapRel);
    1022              : 
    1023           36 :         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           36 :         max_slots = MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_ts_config_map);
    1030           36 :         slot = palloc_array(TupleTableSlot *, max_slots);
    1031              : 
    1032           36 :         ScanKeyInit(&skey,
    1033              :                     Anum_pg_ts_config_map_mapcfg,
    1034              :                     BTEqualStrategyNumber, F_OIDEQ,
    1035              :                     ObjectIdGetDatum(sourceOid));
    1036              : 
    1037           36 :         scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
    1038              :                                   NULL, 1, &skey);
    1039              : 
    1040              :         /* number of slots currently storing tuples */
    1041           36 :         slot_stored_count = 0;
    1042              :         /* number of slots currently initialized */
    1043           36 :         slot_init_count = 0;
    1044              : 
    1045          756 :         while (HeapTupleIsValid((maptup = systable_getnext(scan))))
    1046              :         {
    1047          720 :             Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
    1048              : 
    1049          720 :             if (slot_init_count < max_slots)
    1050              :             {
    1051          720 :                 slot[slot_stored_count] = MakeSingleTupleTableSlot(mapDesc,
    1052              :                                                                    &TTSOpsHeapTuple);
    1053          720 :                 slot_init_count++;
    1054              :             }
    1055              : 
    1056          720 :             ExecClearTuple(slot[slot_stored_count]);
    1057              : 
    1058          720 :             memset(slot[slot_stored_count]->tts_isnull, false,
    1059          720 :                    slot[slot_stored_count]->tts_tupleDescriptor->natts * sizeof(bool));
    1060              : 
    1061          720 :             slot[slot_stored_count]->tts_values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgOid);
    1062          720 :             slot[slot_stored_count]->tts_values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(cfgmap->maptokentype);
    1063          720 :             slot[slot_stored_count]->tts_values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(cfgmap->mapseqno);
    1064          720 :             slot[slot_stored_count]->tts_values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(cfgmap->mapdict);
    1065              : 
    1066          720 :             ExecStoreVirtualTuple(slot[slot_stored_count]);
    1067          720 :             slot_stored_count++;
    1068              : 
    1069              :             /* If slots are full, insert a batch of tuples */
    1070          720 :             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           36 :         if (slot_stored_count > 0)
    1080           36 :             CatalogTuplesMultiInsertWithInfo(mapRel, slot, slot_stored_count,
    1081              :                                              indstate);
    1082              : 
    1083          756 :         for (int i = 0; i < slot_init_count; i++)
    1084          720 :             ExecDropSingleTupleTableSlot(slot[i]);
    1085              : 
    1086           36 :         systable_endscan(scan);
    1087           36 :         CatalogCloseIndexes(indstate);
    1088              :     }
    1089              : 
    1090         1563 :     address = makeConfigurationDependencies(tup, false, mapRel);
    1091              : 
    1092              :     /* Post creation hook for new text search configuration */
    1093         1563 :     InvokeObjectPostCreateHook(TSConfigRelationId, cfgOid, 0);
    1094              : 
    1095         1563 :     heap_freetuple(tup);
    1096              : 
    1097         1563 :     if (mapRel)
    1098           36 :         table_close(mapRel, RowExclusiveLock);
    1099         1563 :     table_close(cfgRel, RowExclusiveLock);
    1100              : 
    1101         1563 :     return address;
    1102              : }
    1103              : 
    1104              : /*
    1105              :  * Guts of TS configuration deletion.
    1106              :  */
    1107              : void
    1108           24 : 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           24 :     relCfg = table_open(TSConfigRelationId, RowExclusiveLock);
    1118              : 
    1119           24 :     tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
    1120              : 
    1121           24 :     if (!HeapTupleIsValid(tup))
    1122            0 :         elog(ERROR, "cache lookup failed for text search dictionary %u",
    1123              :              cfgId);
    1124              : 
    1125           24 :     CatalogTupleDelete(relCfg, &tup->t_self);
    1126              : 
    1127           24 :     ReleaseSysCache(tup);
    1128              : 
    1129           24 :     table_close(relCfg, RowExclusiveLock);
    1130              : 
    1131              :     /* Remove any pg_ts_config_map entries */
    1132           24 :     relMap = table_open(TSConfigMapRelationId, RowExclusiveLock);
    1133              : 
    1134           24 :     ScanKeyInit(&skey,
    1135              :                 Anum_pg_ts_config_map_mapcfg,
    1136              :                 BTEqualStrategyNumber, F_OIDEQ,
    1137              :                 ObjectIdGetDatum(cfgId));
    1138              : 
    1139           24 :     scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
    1140              :                               NULL, 1, &skey);
    1141              : 
    1142          423 :     while (HeapTupleIsValid((tup = systable_getnext(scan))))
    1143              :     {
    1144          399 :         CatalogTupleDelete(relMap, &tup->t_self);
    1145              :     }
    1146              : 
    1147           24 :     systable_endscan(scan);
    1148              : 
    1149           24 :     table_close(relMap, RowExclusiveLock);
    1150           24 : }
    1151              : 
    1152              : /*
    1153              :  * ALTER TEXT SEARCH CONFIGURATION - main entry point
    1154              :  */
    1155              : ObjectAddress
    1156         4677 : AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
    1157              : {
    1158              :     HeapTuple   tup;
    1159              :     Oid         cfgId;
    1160              :     Relation    relMap;
    1161              :     ObjectAddress address;
    1162              : 
    1163              :     /* Find the configuration */
    1164         4677 :     tup = GetTSConfigTuple(stmt->cfgname);
    1165         4677 :     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         4677 :     cfgId = ((Form_pg_ts_config) GETSTRUCT(tup))->oid;
    1172              : 
    1173              :     /* must be owner */
    1174         4677 :     if (!object_ownercheck(TSConfigRelationId, cfgId, GetUserId()))
    1175            0 :         aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TSCONFIGURATION,
    1176            0 :                        NameListToString(stmt->cfgname));
    1177              : 
    1178         4677 :     relMap = table_open(TSConfigMapRelationId, RowExclusiveLock);
    1179              : 
    1180              :     /* Add or drop mappings */
    1181         4677 :     if (stmt->dicts)
    1182         4662 :         MakeConfigurationMapping(stmt, tup, relMap);
    1183           15 :     else if (stmt->tokentype)
    1184           15 :         DropConfigurationMapping(stmt, tup, relMap);
    1185              : 
    1186              :     /* Update dependencies */
    1187         4665 :     makeConfigurationDependencies(tup, true, relMap);
    1188              : 
    1189         4665 :     InvokeObjectPostAlterHook(TSConfigRelationId, cfgId, 0);
    1190              : 
    1191         4665 :     ObjectAddressSet(address, TSConfigRelationId, cfgId);
    1192              : 
    1193         4665 :     table_close(relMap, RowExclusiveLock);
    1194              : 
    1195         4665 :     ReleaseSysCache(tup);
    1196              : 
    1197         4665 :     return address;
    1198              : }
    1199              : 
    1200              : /*
    1201              :  * Check whether a token type name is a member of a TSTokenTypeItem list.
    1202              :  */
    1203              : static bool
    1204        29023 : tstoken_list_member(char *token_name, List *tokens)
    1205              : {
    1206              :     ListCell   *c;
    1207        29023 :     bool        found = false;
    1208              : 
    1209       156751 :     foreach(c, tokens)
    1210              :     {
    1211       127740 :         TSTokenTypeItem *ts = (TSTokenTypeItem *) lfirst(c);
    1212              : 
    1213       127740 :         if (strcmp(token_name, ts->name) == 0)
    1214              :         {
    1215           12 :             found = true;
    1216           12 :             break;
    1217              :         }
    1218              :     }
    1219              : 
    1220        29023 :     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         4677 : getTokenTypes(Oid prsId, List *tokennames)
    1230              : {
    1231         4677 :     TSParserCacheEntry *prs = lookup_ts_parser_cache(prsId);
    1232              :     LexDescr   *list;
    1233         4677 :     List       *result = NIL;
    1234              :     int         ntoken;
    1235              :     ListCell   *tn;
    1236              : 
    1237         4677 :     ntoken = list_length(tokennames);
    1238         4677 :     if (ntoken == 0)
    1239            9 :         return NIL;
    1240              : 
    1241         4668 :     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         4668 :     list = (LexDescr *) DatumGetPointer(OidFunctionCall1(prs->lextypeOid,
    1247              :                                                          (Datum) 0));
    1248              : 
    1249        33682 :     foreach(tn, tokennames)
    1250              :     {
    1251        29023 :         String     *val = lfirst_node(String, tn);
    1252        29023 :         bool        found = false;
    1253              :         int         j;
    1254              : 
    1255              :         /* Skip if this token is already in the result */
    1256        29023 :         if (tstoken_list_member(strVal(val), result))
    1257           12 :             continue;
    1258              : 
    1259        29011 :         j = 0;
    1260       326611 :         while (list && list[j].lexid)
    1261              :         {
    1262       326602 :             if (strcmp(strVal(val), list[j].alias) == 0)
    1263              :             {
    1264        29002 :                 TSTokenTypeItem *ts = palloc0_object(TSTokenTypeItem);
    1265              : 
    1266        29002 :                 ts->num = list[j].lexid;
    1267        29002 :                 ts->name = pstrdup(strVal(val));
    1268        29002 :                 result = lappend(result, ts);
    1269        29002 :                 found = true;
    1270        29002 :                 break;
    1271              :             }
    1272       297600 :             j++;
    1273              :         }
    1274        29011 :         if (!found)
    1275            9 :             ereport(ERROR,
    1276              :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
    1277              :                      errmsg("token type \"%s\" does not exist",
    1278              :                             strVal(val))));
    1279              :     }
    1280              : 
    1281         4659 :     return result;
    1282              : }
    1283              : 
    1284              : /*
    1285              :  * ALTER TEXT SEARCH CONFIGURATION ADD/ALTER MAPPING
    1286              :  */
    1287              : static void
    1288         4662 : 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         4662 :     List       *tokens = NIL;
    1300              :     int         ntoken;
    1301              :     Oid        *dictIds;
    1302              :     int         ndict;
    1303              :     ListCell   *c;
    1304              :     CatalogIndexState indstate;
    1305              : 
    1306         4662 :     tsform = (Form_pg_ts_config) GETSTRUCT(tup);
    1307         4662 :     cfgId = tsform->oid;
    1308         4662 :     prsId = tsform->cfgparser;
    1309              : 
    1310         4662 :     tokens = getTokenTypes(prsId, stmt->tokentype);
    1311         4659 :     ntoken = list_length(tokens);
    1312              : 
    1313         4659 :     if (stmt->override)
    1314              :     {
    1315              :         /*
    1316              :          * delete maps for tokens if they exist and command was ALTER
    1317              :          */
    1318           65 :         foreach(c, tokens)
    1319              :         {
    1320           52 :             TSTokenTypeItem *ts = (TSTokenTypeItem *) lfirst(c);
    1321              : 
    1322           52 :             ScanKeyInit(&skey[0],
    1323              :                         Anum_pg_ts_config_map_mapcfg,
    1324              :                         BTEqualStrategyNumber, F_OIDEQ,
    1325              :                         ObjectIdGetDatum(cfgId));
    1326           52 :             ScanKeyInit(&skey[1],
    1327              :                         Anum_pg_ts_config_map_maptokentype,
    1328              :                         BTEqualStrategyNumber, F_INT4EQ,
    1329              :                         Int32GetDatum(ts->num));
    1330              : 
    1331           52 :             scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
    1332              :                                       NULL, 2, skey);
    1333              : 
    1334          113 :             while (HeapTupleIsValid((maptup = systable_getnext(scan))))
    1335              :             {
    1336           61 :                 CatalogTupleDelete(relMap, &maptup->t_self);
    1337              :             }
    1338              : 
    1339           52 :             systable_endscan(scan);
    1340              :         }
    1341              :     }
    1342              : 
    1343              :     /*
    1344              :      * Convert list of dictionary names to array of dict OIDs
    1345              :      */
    1346         4659 :     ndict = list_length(stmt->dicts);
    1347         4659 :     dictIds = palloc_array(Oid, ndict);
    1348         4659 :     i = 0;
    1349         9366 :     foreach(c, stmt->dicts)
    1350              :     {
    1351         4707 :         List       *names = (List *) lfirst(c);
    1352              : 
    1353         4707 :         dictIds[i] = get_ts_dict_oid(names, false);
    1354         4707 :         i++;
    1355              :     }
    1356              : 
    1357         4659 :     indstate = CatalogOpenIndexes(relMap);
    1358              : 
    1359         4659 :     if (stmt->replace)
    1360              :     {
    1361              :         /*
    1362              :          * Replace a specific dictionary in existing entries
    1363              :          */
    1364            9 :         Oid         dictOld = dictIds[0],
    1365            9 :                     dictNew = dictIds[1];
    1366              : 
    1367            9 :         ScanKeyInit(&skey[0],
    1368              :                     Anum_pg_ts_config_map_mapcfg,
    1369              :                     BTEqualStrategyNumber, F_OIDEQ,
    1370              :                     ObjectIdGetDatum(cfgId));
    1371              : 
    1372            9 :         scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
    1373              :                                   NULL, 1, skey);
    1374              : 
    1375          261 :         while (HeapTupleIsValid((maptup = systable_getnext(scan))))
    1376              :         {
    1377          252 :             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          252 :             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          252 :             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           81 :                 memset(repl_val, 0, sizeof(repl_val));
    1411           81 :                 memset(repl_null, false, sizeof(repl_null));
    1412           81 :                 memset(repl_repl, false, sizeof(repl_repl));
    1413              : 
    1414           81 :                 repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew);
    1415           81 :                 repl_repl[Anum_pg_ts_config_map_mapdict - 1] = true;
    1416              : 
    1417           81 :                 newtup = heap_modify_tuple(maptup,
    1418              :                                            RelationGetDescr(relMap),
    1419              :                                            repl_val, repl_null, repl_repl);
    1420           81 :                 CatalogTupleUpdateWithInfo(relMap, &newtup->t_self, newtup, indstate);
    1421              :             }
    1422              :         }
    1423              : 
    1424            9 :         systable_endscan(scan);
    1425              :     }
    1426              :     else
    1427              :     {
    1428              :         TupleTableSlot **slot;
    1429         4650 :         int         slotCount = 0;
    1430              :         int         nslots;
    1431              : 
    1432              :         /* Allocate the slots to use and initialize them */
    1433         4650 :         nslots = Min(ntoken * ndict,
    1434              :                      MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_ts_config_map));
    1435         4650 :         slot = palloc_array(TupleTableSlot *, nslots);
    1436        33724 :         for (i = 0; i < nslots; i++)
    1437        29074 :             slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(relMap),
    1438              :                                                &TTSOpsHeapTuple);
    1439              : 
    1440              :         /*
    1441              :          * Insertion of new entries
    1442              :          */
    1443        33643 :         foreach(c, tokens)
    1444              :         {
    1445        28993 :             TSTokenTypeItem *ts = (TSTokenTypeItem *) lfirst(c);
    1446              : 
    1447        58067 :             for (j = 0; j < ndict; j++)
    1448              :             {
    1449        29074 :                 ExecClearTuple(slot[slotCount]);
    1450              : 
    1451        29074 :                 memset(slot[slotCount]->tts_isnull, false,
    1452        29074 :                        slot[slotCount]->tts_tupleDescriptor->natts * sizeof(bool));
    1453              : 
    1454        29074 :                 slot[slotCount]->tts_values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId);
    1455        29074 :                 slot[slotCount]->tts_values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(ts->num);
    1456        29074 :                 slot[slotCount]->tts_values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1);
    1457        29074 :                 slot[slotCount]->tts_values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]);
    1458              : 
    1459        29074 :                 ExecStoreVirtualTuple(slot[slotCount]);
    1460        29074 :                 slotCount++;
    1461              : 
    1462              :                 /* If slots are full, insert a batch of tuples */
    1463        29074 :                 if (slotCount == nslots)
    1464              :                 {
    1465         4650 :                     CatalogTuplesMultiInsertWithInfo(relMap, slot, slotCount,
    1466              :                                                      indstate);
    1467         4650 :                     slotCount = 0;
    1468              :                 }
    1469              :             }
    1470              :         }
    1471              : 
    1472              :         /* Insert any tuples left in the buffer */
    1473         4650 :         if (slotCount > 0)
    1474            0 :             CatalogTuplesMultiInsertWithInfo(relMap, slot, slotCount,
    1475              :                                              indstate);
    1476              : 
    1477        33724 :         for (i = 0; i < nslots; i++)
    1478        29074 :             ExecDropSingleTupleTableSlot(slot[i]);
    1479              :     }
    1480              : 
    1481              :     /* clean up */
    1482         4659 :     CatalogCloseIndexes(indstate);
    1483              : 
    1484         4659 :     EventTriggerCollectAlterTSConfig(stmt, cfgId, dictIds, ndict);
    1485         4659 : }
    1486              : 
    1487              : /*
    1488              :  * ALTER TEXT SEARCH CONFIGURATION DROP MAPPING
    1489              :  */
    1490              : static void
    1491           15 : 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           15 :     List       *tokens = NIL;
    1501              :     ListCell   *c;
    1502              : 
    1503           15 :     tsform = (Form_pg_ts_config) GETSTRUCT(tup);
    1504           15 :     cfgId = tsform->oid;
    1505           15 :     prsId = tsform->cfgparser;
    1506              : 
    1507           15 :     tokens = getTokenTypes(prsId, stmt->tokentype);
    1508              : 
    1509           15 :     foreach(c, tokens)
    1510              :     {
    1511            9 :         TSTokenTypeItem *ts = (TSTokenTypeItem *) lfirst(c);
    1512            9 :         bool        found = false;
    1513              : 
    1514            9 :         ScanKeyInit(&skey[0],
    1515              :                     Anum_pg_ts_config_map_mapcfg,
    1516              :                     BTEqualStrategyNumber, F_OIDEQ,
    1517              :                     ObjectIdGetDatum(cfgId));
    1518            9 :         ScanKeyInit(&skey[1],
    1519              :                     Anum_pg_ts_config_map_maptokentype,
    1520              :                     BTEqualStrategyNumber, F_INT4EQ,
    1521              :                     Int32GetDatum(ts->num));
    1522              : 
    1523            9 :         scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
    1524              :                                   NULL, 2, skey);
    1525              : 
    1526           12 :         while (HeapTupleIsValid((maptup = systable_getnext(scan))))
    1527              :         {
    1528            3 :             CatalogTupleDelete(relMap, &maptup->t_self);
    1529            3 :             found = true;
    1530              :         }
    1531              : 
    1532            9 :         systable_endscan(scan);
    1533              : 
    1534            9 :         if (!found)
    1535              :         {
    1536            6 :             if (!stmt->missing_ok)
    1537              :             {
    1538            3 :                 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            3 :                 ereport(NOTICE,
    1546              :                         (errmsg("mapping for token type \"%s\" does not exist, skipping",
    1547              :                                 ts->name)));
    1548              :             }
    1549              :         }
    1550              :     }
    1551              : 
    1552            6 :     EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0);
    1553            6 : }
    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         1575 : serialize_deflist(List *deflist)
    1566              : {
    1567              :     text       *result;
    1568              :     StringInfoData buf;
    1569              :     ListCell   *l;
    1570              : 
    1571         1575 :     initStringInfo(&buf);
    1572              : 
    1573         3958 :     foreach(l, deflist)
    1574              :     {
    1575         2383 :         DefElem    *defel = (DefElem *) lfirst(l);
    1576         2383 :         char       *val = defGetString(defel);
    1577              : 
    1578         2383 :         appendStringInfo(&buf, "%s = ",
    1579         2383 :                          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         2383 :         if (IsA(defel->arg, Integer) || IsA(defel->arg, Float))
    1587            7 :             appendStringInfoString(&buf, val);
    1588              :         else
    1589              :         {
    1590              :             /* If backslashes appear, force E syntax to quote them safely */
    1591         2376 :             if (strchr(val, '\\'))
    1592            0 :                 appendStringInfoChar(&buf, ESCAPE_STRING_SYNTAX);
    1593         2376 :             appendStringInfoChar(&buf, '\'');
    1594        19755 :             while (*val)
    1595              :             {
    1596        17379 :                 char        ch = *val++;
    1597              : 
    1598        17379 :                 if (SQL_STR_DOUBLE(ch, true))
    1599            0 :                     appendStringInfoChar(&buf, ch);
    1600        17379 :                 appendStringInfoChar(&buf, ch);
    1601              :             }
    1602         2376 :             appendStringInfoChar(&buf, '\'');
    1603              :         }
    1604         2383 :         if (lnext(deflist, l) != NULL)
    1605          808 :             appendStringInfoString(&buf, ", ");
    1606              :     }
    1607              : 
    1608         1575 :     result = cstring_to_text_with_len(buf.data, buf.len);
    1609         1575 :     pfree(buf.data);
    1610         1575 :     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          131 : deserialize_deflist(Datum txt)
    1622              : {
    1623          131 :     text       *in = DatumGetTextPP(txt);   /* in case it's toasted */
    1624          131 :     List       *result = NIL;
    1625          131 :     int         len = VARSIZE_ANY_EXHDR(in);
    1626              :     char       *ptr,
    1627              :                *endptr,
    1628              :                *workspace,
    1629          131 :                *wsptr = NULL,
    1630          131 :                *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          131 :     ds_state    state = CS_WAITKEY;
    1643              : 
    1644          131 :     workspace = (char *) palloc(len + 1);   /* certainly enough room */
    1645          131 :     ptr = VARDATA_ANY(in);
    1646          131 :     endptr = ptr + len;
    1647         6049 :     for (; ptr < endptr; ptr++)
    1648              :     {
    1649         5918 :         switch (state)
    1650              :         {
    1651          548 :             case CS_WAITKEY:
    1652          548 :                 if (isspace((unsigned char) *ptr) || *ptr == ',')
    1653          264 :                     continue;
    1654          284 :                 if (*ptr == '"')
    1655              :                 {
    1656            0 :                     wsptr = workspace;
    1657            0 :                     state = CS_INQKEY;
    1658              :                 }
    1659              :                 else
    1660              :                 {
    1661          284 :                     wsptr = workspace;
    1662          284 :                     *wsptr++ = *ptr;
    1663          284 :                     state = CS_INKEY;
    1664              :                 }
    1665          284 :                 break;
    1666         2509 :             case CS_INKEY:
    1667         2509 :                 if (isspace((unsigned char) *ptr))
    1668              :                 {
    1669          227 :                     *wsptr++ = '\0';
    1670          227 :                     state = CS_WAITEQ;
    1671              :                 }
    1672         2282 :                 else if (*ptr == '=')
    1673              :                 {
    1674           57 :                     *wsptr++ = '\0';
    1675           57 :                     state = CS_WAITVALUE;
    1676              :                 }
    1677              :                 else
    1678              :                 {
    1679         2225 :                     *wsptr++ = *ptr;
    1680              :                 }
    1681         2509 :                 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          227 :             case CS_WAITEQ:
    1702          227 :                 if (*ptr == '=')
    1703          227 :                     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          227 :                 break;
    1710          511 :             case CS_WAITVALUE:
    1711          511 :                 if (*ptr == '\'')
    1712              :                 {
    1713          188 :                     startvalue = wsptr;
    1714          188 :                     state = CS_INSQVALUE;
    1715              :                 }
    1716          323 :                 else if (*ptr == 'E' && ptr + 1 < endptr && ptr[1] == '\'')
    1717              :                 {
    1718            0 :                     ptr++;
    1719            0 :                     startvalue = wsptr;
    1720            0 :                     state = CS_INSQVALUE;
    1721              :                 }
    1722          323 :                 else if (*ptr == '"')
    1723              :                 {
    1724            0 :                     startvalue = wsptr;
    1725            0 :                     state = CS_INDQVALUE;
    1726              :                 }
    1727          323 :                 else if (!isspace((unsigned char) *ptr))
    1728              :                 {
    1729           96 :                     startvalue = wsptr;
    1730           96 :                     *wsptr++ = *ptr;
    1731           96 :                     state = CS_INWVALUE;
    1732              :                 }
    1733          511 :                 break;
    1734         2035 :             case CS_INSQVALUE:
    1735         2035 :                 if (*ptr == '\'')
    1736              :                 {
    1737          188 :                     if (ptr + 1 < endptr && ptr[1] == '\'')
    1738              :                     {
    1739              :                         /* copy only one of the two quotes */
    1740            0 :                         *wsptr++ = *ptr++;
    1741              :                     }
    1742              :                     else
    1743              :                     {
    1744          188 :                         *wsptr++ = '\0';
    1745          188 :                         result = lappend(result,
    1746          188 :                                          buildDefItem(workspace,
    1747              :                                                       startvalue,
    1748              :                                                       true));
    1749          188 :                         state = CS_WAITKEY;
    1750              :                     }
    1751              :                 }
    1752         1847 :                 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         1847 :                     *wsptr++ = *ptr;
    1765              :                 }
    1766         2035 :                 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           88 :             case CS_INWVALUE:
    1791           88 :                 if (*ptr == ',' || isspace((unsigned char) *ptr))
    1792              :                 {
    1793           39 :                     *wsptr++ = '\0';
    1794           39 :                     result = lappend(result,
    1795           39 :                                      buildDefItem(workspace,
    1796              :                                                   startvalue,
    1797              :                                                   false));
    1798           39 :                     state = CS_WAITKEY;
    1799              :                 }
    1800              :                 else
    1801              :                 {
    1802           49 :                     *wsptr++ = *ptr;
    1803              :                 }
    1804           88 :                 break;
    1805            0 :             default:
    1806            0 :                 elog(ERROR, "unrecognized deserialize_deflist state: %d",
    1807              :                      state);
    1808              :         }
    1809              :     }
    1810              : 
    1811          131 :     if (state == CS_INWVALUE)
    1812              :     {
    1813           57 :         *wsptr++ = '\0';
    1814           57 :         result = lappend(result,
    1815           57 :                          buildDefItem(workspace,
    1816              :                                       startvalue,
    1817              :                                       false));
    1818              :     }
    1819           74 :     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          131 :     pfree(workspace);
    1826              : 
    1827          131 :     return result;
    1828              : }
    1829              : 
    1830              : /*
    1831              :  * Build one DefElem for deserialize_deflist
    1832              :  */
    1833              : static DefElem *
    1834          284 : buildDefItem(const char *name, const char *val, bool was_quoted)
    1835              : {
    1836              :     /* If input was quoted, always emit as string */
    1837          284 :     if (!was_quoted && val[0] != '\0')
    1838              :     {
    1839              :         int         v;
    1840              :         char       *endptr;
    1841              : 
    1842              :         /* Try to parse as an integer */
    1843           96 :         errno = 0;
    1844           96 :         v = strtoint(val, &endptr, 10);
    1845           96 :         if (errno == 0 && *endptr == '\0')
    1846           69 :             return makeDefElem(pstrdup(name),
    1847           61 :                                (Node *) makeInteger(v),
    1848              :                                -1);
    1849              :         /* Nope, how about as a float? */
    1850           35 :         errno = 0;
    1851           35 :         (void) strtod(val, &endptr);
    1852           35 :         if (errno == 0 && *endptr == '\0')
    1853            5 :             return makeDefElem(pstrdup(name),
    1854            5 :                                (Node *) makeFloat(pstrdup(val)),
    1855              :                                -1);
    1856              : 
    1857           30 :         if (strcmp(val, "true") == 0)
    1858            3 :             return makeDefElem(pstrdup(name),
    1859            3 :                                (Node *) makeBoolean(true),
    1860              :                                -1);
    1861           27 :         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          215 :     return makeDefElem(pstrdup(name),
    1868          215 :                        (Node *) makeString(pstrdup(val)),
    1869              :                        -1);
    1870              : }
        

Generated by: LCOV version 2.0-1