]> granicus.if.org Git - postgresql/blob - src/backend/commands/tsearchcmds.c
Consolidate DROP handling for some object types.
[postgresql] / src / backend / commands / tsearchcmds.c
1 /*-------------------------------------------------------------------------
2  *
3  * tsearchcmds.c
4  *
5  *        Routines for tsearch manipulation commands
6  *
7  * Portions Copyright (c) 1996-2011, 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/heapam.h"
21 #include "access/genam.h"
22 #include "access/xact.h"
23 #include "catalog/dependency.h"
24 #include "catalog/indexing.h"
25 #include "catalog/objectaccess.h"
26 #include "catalog/pg_namespace.h"
27 #include "catalog/pg_proc.h"
28 #include "catalog/pg_ts_config.h"
29 #include "catalog/pg_ts_config_map.h"
30 #include "catalog/pg_ts_dict.h"
31 #include "catalog/pg_ts_parser.h"
32 #include "catalog/pg_ts_template.h"
33 #include "catalog/pg_type.h"
34 #include "commands/alter.h"
35 #include "commands/defrem.h"
36 #include "miscadmin.h"
37 #include "nodes/makefuncs.h"
38 #include "parser/parse_func.h"
39 #include "tsearch/ts_cache.h"
40 #include "tsearch/ts_utils.h"
41 #include "utils/builtins.h"
42 #include "utils/fmgroids.h"
43 #include "utils/lsyscache.h"
44 #include "utils/rel.h"
45 #include "utils/syscache.h"
46 #include "utils/tqual.h"
47
48
49 static void MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
50                                                  HeapTuple tup, Relation relMap);
51 static void DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
52                                                  HeapTuple tup, Relation relMap);
53
54
55 /* --------------------- TS Parser commands ------------------------ */
56
57 /*
58  * lookup a parser support function and return its OID (as a Datum)
59  *
60  * attnum is the pg_ts_parser column the function will go into
61  */
62 static Datum
63 get_ts_parser_func(DefElem *defel, int attnum)
64 {
65         List       *funcName = defGetQualifiedName(defel);
66         Oid                     typeId[3];
67         Oid                     retTypeId;
68         int                     nargs;
69         Oid                     procOid;
70
71         retTypeId = INTERNALOID;        /* correct for most */
72         typeId[0] = INTERNALOID;
73         switch (attnum)
74         {
75                 case Anum_pg_ts_parser_prsstart:
76                         nargs = 2;
77                         typeId[1] = INT4OID;
78                         break;
79                 case Anum_pg_ts_parser_prstoken:
80                         nargs = 3;
81                         typeId[1] = INTERNALOID;
82                         typeId[2] = INTERNALOID;
83                         break;
84                 case Anum_pg_ts_parser_prsend:
85                         nargs = 1;
86                         retTypeId = VOIDOID;
87                         break;
88                 case Anum_pg_ts_parser_prsheadline:
89                         nargs = 3;
90                         typeId[1] = INTERNALOID;
91                         typeId[2] = TSQUERYOID;
92                         break;
93                 case Anum_pg_ts_parser_prslextype:
94                         nargs = 1;
95
96                         /*
97                          * Note: because the lextype method returns type internal, it must
98                          * have an internal-type argument for security reasons.  The
99                          * argument is not actually used, but is just passed as a zero.
100                          */
101                         break;
102                 default:
103                         /* should not be here */
104                         elog(ERROR, "unrecognized attribute for text search parser: %d",
105                                  attnum);
106                         nargs = 0;                      /* keep compiler quiet */
107         }
108
109         procOid = LookupFuncName(funcName, nargs, typeId, false);
110         if (get_func_rettype(procOid) != retTypeId)
111                 ereport(ERROR,
112                                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
113                                  errmsg("function %s should return type %s",
114                                                 func_signature_string(funcName, nargs, NIL, typeId),
115                                                 format_type_be(retTypeId))));
116
117         return ObjectIdGetDatum(procOid);
118 }
119
120 /*
121  * make pg_depend entries for a new pg_ts_parser entry
122  */
123 static void
124 makeParserDependencies(HeapTuple tuple)
125 {
126         Form_pg_ts_parser prs = (Form_pg_ts_parser) GETSTRUCT(tuple);
127         ObjectAddress myself,
128                                 referenced;
129
130         myself.classId = TSParserRelationId;
131         myself.objectId = HeapTupleGetOid(tuple);
132         myself.objectSubId = 0;
133
134         /* dependency on namespace */
135         referenced.classId = NamespaceRelationId;
136         referenced.objectId = prs->prsnamespace;
137         referenced.objectSubId = 0;
138         recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
139
140         /* dependency on extension */
141         recordDependencyOnCurrentExtension(&myself, false);
142
143         /* dependencies on functions */
144         referenced.classId = ProcedureRelationId;
145         referenced.objectSubId = 0;
146
147         referenced.objectId = prs->prsstart;
148         recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
149
150         referenced.objectId = prs->prstoken;
151         recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
152
153         referenced.objectId = prs->prsend;
154         recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
155
156         referenced.objectId = prs->prslextype;
157         recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
158
159         if (OidIsValid(prs->prsheadline))
160         {
161                 referenced.objectId = prs->prsheadline;
162                 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
163         }
164 }
165
166 /*
167  * CREATE TEXT SEARCH PARSER
168  */
169 void
170 DefineTSParser(List *names, List *parameters)
171 {
172         char       *prsname;
173         ListCell   *pl;
174         Relation        prsRel;
175         HeapTuple       tup;
176         Datum           values[Natts_pg_ts_parser];
177         bool            nulls[Natts_pg_ts_parser];
178         NameData        pname;
179         Oid                     prsOid;
180         Oid                     namespaceoid;
181
182         if (!superuser())
183                 ereport(ERROR,
184                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
185                                  errmsg("must be superuser to create text search parsers")));
186
187         /* Convert list of names to a name and namespace */
188         namespaceoid = QualifiedNameGetCreationNamespace(names, &prsname);
189
190         /* initialize tuple fields with name/namespace */
191         memset(values, 0, sizeof(values));
192         memset(nulls, false, sizeof(nulls));
193
194         namestrcpy(&pname, prsname);
195         values[Anum_pg_ts_parser_prsname - 1] = NameGetDatum(&pname);
196         values[Anum_pg_ts_parser_prsnamespace - 1] = ObjectIdGetDatum(namespaceoid);
197
198         /*
199          * loop over the definition list and extract the information we need.
200          */
201         foreach(pl, parameters)
202         {
203                 DefElem    *defel = (DefElem *) lfirst(pl);
204
205                 if (pg_strcasecmp(defel->defname, "start") == 0)
206                 {
207                         values[Anum_pg_ts_parser_prsstart - 1] =
208                                 get_ts_parser_func(defel, Anum_pg_ts_parser_prsstart);
209                 }
210                 else if (pg_strcasecmp(defel->defname, "gettoken") == 0)
211                 {
212                         values[Anum_pg_ts_parser_prstoken - 1] =
213                                 get_ts_parser_func(defel, Anum_pg_ts_parser_prstoken);
214                 }
215                 else if (pg_strcasecmp(defel->defname, "end") == 0)
216                 {
217                         values[Anum_pg_ts_parser_prsend - 1] =
218                                 get_ts_parser_func(defel, Anum_pg_ts_parser_prsend);
219                 }
220                 else if (pg_strcasecmp(defel->defname, "headline") == 0)
221                 {
222                         values[Anum_pg_ts_parser_prsheadline - 1] =
223                                 get_ts_parser_func(defel, Anum_pg_ts_parser_prsheadline);
224                 }
225                 else if (pg_strcasecmp(defel->defname, "lextypes") == 0)
226                 {
227                         values[Anum_pg_ts_parser_prslextype - 1] =
228                                 get_ts_parser_func(defel, Anum_pg_ts_parser_prslextype);
229                 }
230                 else
231                         ereport(ERROR,
232                                         (errcode(ERRCODE_SYNTAX_ERROR),
233                                  errmsg("text search parser parameter \"%s\" not recognized",
234                                                 defel->defname)));
235         }
236
237         /*
238          * Validation
239          */
240         if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsstart - 1])))
241                 ereport(ERROR,
242                                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
243                                  errmsg("text search parser start method is required")));
244
245         if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prstoken - 1])))
246                 ereport(ERROR,
247                                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
248                                  errmsg("text search parser gettoken method is required")));
249
250         if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsend - 1])))
251                 ereport(ERROR,
252                                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
253                                  errmsg("text search parser end method is required")));
254
255         if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prslextype - 1])))
256                 ereport(ERROR,
257                                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
258                                  errmsg("text search parser lextypes method is required")));
259
260         /*
261          * Looks good, insert
262          */
263         prsRel = heap_open(TSParserRelationId, RowExclusiveLock);
264
265         tup = heap_form_tuple(prsRel->rd_att, values, nulls);
266
267         prsOid = simple_heap_insert(prsRel, tup);
268
269         CatalogUpdateIndexes(prsRel, tup);
270
271         makeParserDependencies(tup);
272
273         /* Post creation hook for new text search parser */
274         InvokeObjectAccessHook(OAT_POST_CREATE, TSParserRelationId, prsOid, 0);
275
276         heap_freetuple(tup);
277
278         heap_close(prsRel, RowExclusiveLock);
279 }
280
281 /*
282  * Guts of TS parser deletion.
283  */
284 void
285 RemoveTSParserById(Oid prsId)
286 {
287         Relation        relation;
288         HeapTuple       tup;
289
290         relation = heap_open(TSParserRelationId, RowExclusiveLock);
291
292         tup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId));
293
294         if (!HeapTupleIsValid(tup))
295                 elog(ERROR, "cache lookup failed for text search parser %u", prsId);
296
297         simple_heap_delete(relation, &tup->t_self);
298
299         ReleaseSysCache(tup);
300
301         heap_close(relation, RowExclusiveLock);
302 }
303
304 /*
305  * ALTER TEXT SEARCH PARSER RENAME
306  */
307 void
308 RenameTSParser(List *oldname, const char *newname)
309 {
310         HeapTuple       tup;
311         Relation        rel;
312         Oid                     prsId;
313         Oid                     namespaceOid;
314
315         if (!superuser())
316                 ereport(ERROR,
317                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
318                                  errmsg("must be superuser to rename text search parsers")));
319
320         rel = heap_open(TSParserRelationId, RowExclusiveLock);
321
322         prsId = get_ts_parser_oid(oldname, false);
323
324         tup = SearchSysCacheCopy1(TSPARSEROID, ObjectIdGetDatum(prsId));
325
326         if (!HeapTupleIsValid(tup)) /* should not happen */
327                 elog(ERROR, "cache lookup failed for text search parser %u", prsId);
328
329         namespaceOid = ((Form_pg_ts_parser) GETSTRUCT(tup))->prsnamespace;
330
331         if (SearchSysCacheExists2(TSPARSERNAMENSP,
332                                                           PointerGetDatum(newname),
333                                                           ObjectIdGetDatum(namespaceOid)))
334                 ereport(ERROR,
335                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
336                                  errmsg("text search parser \"%s\" already exists",
337                                                 newname)));
338
339         namestrcpy(&(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname), newname);
340         simple_heap_update(rel, &tup->t_self, tup);
341         CatalogUpdateIndexes(rel, tup);
342
343         heap_close(rel, NoLock);
344         heap_freetuple(tup);
345 }
346
347 /*
348  * ALTER TEXT SEARCH PARSER any_name SET SCHEMA name
349  */
350 void
351 AlterTSParserNamespace(List *name, const char *newschema)
352 {
353         Oid                     prsId,
354                                 nspOid;
355         Relation        rel;
356
357         rel = heap_open(TSParserRelationId, RowExclusiveLock);
358
359         prsId = get_ts_parser_oid(name, false);
360
361         /* get schema OID */
362         nspOid = LookupCreationNamespace(newschema);
363
364         AlterObjectNamespace(rel, TSPARSEROID, TSPARSERNAMENSP,
365                                                  prsId, nspOid,
366                                                  Anum_pg_ts_parser_prsname,
367                                                  Anum_pg_ts_parser_prsnamespace,
368                                                  -1, -1);
369
370         heap_close(rel, RowExclusiveLock);
371 }
372
373 Oid
374 AlterTSParserNamespace_oid(Oid prsId, Oid newNspOid)
375 {
376         Oid                     oldNspOid;
377         Relation        rel;
378
379         rel = heap_open(TSParserRelationId, RowExclusiveLock);
380
381         oldNspOid =
382                 AlterObjectNamespace(rel, TSPARSEROID, TSPARSERNAMENSP,
383                                                          prsId, newNspOid,
384                                                          Anum_pg_ts_parser_prsname,
385                                                          Anum_pg_ts_parser_prsnamespace,
386                                                          -1, -1);
387
388         heap_close(rel, RowExclusiveLock);
389
390         return oldNspOid;
391 }
392
393 /* ---------------------- TS Dictionary commands -----------------------*/
394
395 /*
396  * make pg_depend entries for a new pg_ts_dict entry
397  */
398 static void
399 makeDictionaryDependencies(HeapTuple tuple)
400 {
401         Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tuple);
402         ObjectAddress myself,
403                                 referenced;
404
405         myself.classId = TSDictionaryRelationId;
406         myself.objectId = HeapTupleGetOid(tuple);
407         myself.objectSubId = 0;
408
409         /* dependency on namespace */
410         referenced.classId = NamespaceRelationId;
411         referenced.objectId = dict->dictnamespace;
412         referenced.objectSubId = 0;
413         recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
414
415         /* dependency on owner */
416         recordDependencyOnOwner(myself.classId, myself.objectId, dict->dictowner);
417
418         /* dependency on extension */
419         recordDependencyOnCurrentExtension(&myself, false);
420
421         /* dependency on template */
422         referenced.classId = TSTemplateRelationId;
423         referenced.objectId = dict->dicttemplate;
424         referenced.objectSubId = 0;
425         recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
426 }
427
428 /*
429  * verify that a template's init method accepts a proposed option list
430  */
431 static void
432 verify_dictoptions(Oid tmplId, List *dictoptions)
433 {
434         HeapTuple       tup;
435         Form_pg_ts_template tform;
436         Oid                     initmethod;
437
438         /*
439          * Suppress this test when running in a standalone backend.  This is a
440          * hack to allow initdb to create prefab dictionaries that might not
441          * actually be usable in template1's encoding (due to using external files
442          * that can't be translated into template1's encoding).  We want to create
443          * them anyway, since they might be usable later in other databases.
444          */
445         if (!IsUnderPostmaster)
446                 return;
447
448         tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId));
449         if (!HeapTupleIsValid(tup)) /* should not happen */
450                 elog(ERROR, "cache lookup failed for text search template %u",
451                          tmplId);
452         tform = (Form_pg_ts_template) GETSTRUCT(tup);
453
454         initmethod = tform->tmplinit;
455
456         if (!OidIsValid(initmethod))
457         {
458                 /* If there is no init method, disallow any options */
459                 if (dictoptions)
460                         ereport(ERROR,
461                                         (errcode(ERRCODE_SYNTAX_ERROR),
462                                 errmsg("text search template \"%s\" does not accept options",
463                                            NameStr(tform->tmplname))));
464         }
465         else
466         {
467                 /*
468                  * Copy the options just in case init method thinks it can scribble on
469                  * them ...
470                  */
471                 dictoptions = copyObject(dictoptions);
472
473                 /*
474                  * Call the init method and see if it complains.  We don't worry about
475                  * it leaking memory, since our command will soon be over anyway.
476                  */
477                 (void) OidFunctionCall1(initmethod, PointerGetDatum(dictoptions));
478         }
479
480         ReleaseSysCache(tup);
481 }
482
483 /*
484  * CREATE TEXT SEARCH DICTIONARY
485  */
486 void
487 DefineTSDictionary(List *names, List *parameters)
488 {
489         ListCell   *pl;
490         Relation        dictRel;
491         HeapTuple       tup;
492         Datum           values[Natts_pg_ts_dict];
493         bool            nulls[Natts_pg_ts_dict];
494         NameData        dname;
495         Oid                     templId = InvalidOid;
496         List       *dictoptions = NIL;
497         Oid                     dictOid;
498         Oid                     namespaceoid;
499         AclResult       aclresult;
500         char       *dictname;
501
502         /* Convert list of names to a name and namespace */
503         namespaceoid = QualifiedNameGetCreationNamespace(names, &dictname);
504
505         /* Check we have creation rights in target namespace */
506         aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
507         if (aclresult != ACLCHECK_OK)
508                 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
509                                            get_namespace_name(namespaceoid));
510
511         /*
512          * loop over the definition list and extract the information we need.
513          */
514         foreach(pl, parameters)
515         {
516                 DefElem    *defel = (DefElem *) lfirst(pl);
517
518                 if (pg_strcasecmp(defel->defname, "template") == 0)
519                 {
520                         templId = get_ts_template_oid(defGetQualifiedName(defel), false);
521                 }
522                 else
523                 {
524                         /* Assume it's an option for the dictionary itself */
525                         dictoptions = lappend(dictoptions, defel);
526                 }
527         }
528
529         /*
530          * Validation
531          */
532         if (!OidIsValid(templId))
533                 ereport(ERROR,
534                                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
535                                  errmsg("text search template is required")));
536
537         verify_dictoptions(templId, dictoptions);
538
539         /*
540          * Looks good, insert
541          */
542         memset(values, 0, sizeof(values));
543         memset(nulls, false, sizeof(nulls));
544
545         namestrcpy(&dname, dictname);
546         values[Anum_pg_ts_dict_dictname - 1] = NameGetDatum(&dname);
547         values[Anum_pg_ts_dict_dictnamespace - 1] = ObjectIdGetDatum(namespaceoid);
548         values[Anum_pg_ts_dict_dictowner - 1] = ObjectIdGetDatum(GetUserId());
549         values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(templId);
550         if (dictoptions)
551                 values[Anum_pg_ts_dict_dictinitoption - 1] =
552                         PointerGetDatum(serialize_deflist(dictoptions));
553         else
554                 nulls[Anum_pg_ts_dict_dictinitoption - 1] = true;
555
556         dictRel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
557
558         tup = heap_form_tuple(dictRel->rd_att, values, nulls);
559
560         dictOid = simple_heap_insert(dictRel, tup);
561
562         CatalogUpdateIndexes(dictRel, tup);
563
564         makeDictionaryDependencies(tup);
565
566         /* Post creation hook for new text search dictionary */
567         InvokeObjectAccessHook(OAT_POST_CREATE,
568                                                    TSDictionaryRelationId, dictOid, 0);
569
570         heap_freetuple(tup);
571
572         heap_close(dictRel, RowExclusiveLock);
573 }
574
575 /*
576  * ALTER TEXT SEARCH DICTIONARY RENAME
577  */
578 void
579 RenameTSDictionary(List *oldname, const char *newname)
580 {
581         HeapTuple       tup;
582         Relation        rel;
583         Oid                     dictId;
584         Oid                     namespaceOid;
585         AclResult       aclresult;
586
587         rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
588
589         dictId = get_ts_dict_oid(oldname, false);
590
591         tup = SearchSysCacheCopy1(TSDICTOID, ObjectIdGetDatum(dictId));
592
593         if (!HeapTupleIsValid(tup)) /* should not happen */
594                 elog(ERROR, "cache lookup failed for text search dictionary %u",
595                          dictId);
596
597         namespaceOid = ((Form_pg_ts_dict) GETSTRUCT(tup))->dictnamespace;
598
599         if (SearchSysCacheExists2(TSDICTNAMENSP,
600                                                           PointerGetDatum(newname),
601                                                           ObjectIdGetDatum(namespaceOid)))
602                 ereport(ERROR,
603                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
604                                  errmsg("text search dictionary \"%s\" already exists",
605                                                 newname)));
606
607         /* must be owner */
608         if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
609                 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
610                                            NameListToString(oldname));
611
612         /* must have CREATE privilege on namespace */
613         aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);
614         if (aclresult != ACLCHECK_OK)
615                 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
616                                            get_namespace_name(namespaceOid));
617
618         namestrcpy(&(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname), newname);
619         simple_heap_update(rel, &tup->t_self, tup);
620         CatalogUpdateIndexes(rel, tup);
621
622         heap_close(rel, NoLock);
623         heap_freetuple(tup);
624 }
625
626 /*
627  * ALTER TEXT SEARCH DICTIONARY any_name SET SCHEMA name
628  */
629 void
630 AlterTSDictionaryNamespace(List *name, const char *newschema)
631 {
632         Oid                     dictId,
633                                 nspOid;
634         Relation        rel;
635
636         rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
637
638         dictId = get_ts_dict_oid(name, false);
639
640         /* get schema OID */
641         nspOid = LookupCreationNamespace(newschema);
642
643         AlterObjectNamespace(rel, TSDICTOID, TSDICTNAMENSP,
644                                                  dictId, nspOid,
645                                                  Anum_pg_ts_dict_dictname,
646                                                  Anum_pg_ts_dict_dictnamespace,
647                                                  Anum_pg_ts_dict_dictowner,
648                                                  ACL_KIND_TSDICTIONARY);
649
650         heap_close(rel, RowExclusiveLock);
651 }
652
653 Oid
654 AlterTSDictionaryNamespace_oid(Oid dictId, Oid newNspOid)
655 {
656         Oid                     oldNspOid;
657         Relation        rel;
658
659         rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
660
661         oldNspOid =
662                 AlterObjectNamespace(rel, TSDICTOID, TSDICTNAMENSP,
663                                                          dictId, newNspOid,
664                                                          Anum_pg_ts_dict_dictname,
665                                                          Anum_pg_ts_dict_dictnamespace,
666                                                          Anum_pg_ts_dict_dictowner,
667                                                          ACL_KIND_TSDICTIONARY);
668
669         heap_close(rel, RowExclusiveLock);
670
671         return oldNspOid;
672 }
673
674 /*
675  * Guts of TS dictionary deletion.
676  */
677 void
678 RemoveTSDictionaryById(Oid dictId)
679 {
680         Relation        relation;
681         HeapTuple       tup;
682
683         relation = heap_open(TSDictionaryRelationId, RowExclusiveLock);
684
685         tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
686
687         if (!HeapTupleIsValid(tup))
688                 elog(ERROR, "cache lookup failed for text search dictionary %u",
689                          dictId);
690
691         simple_heap_delete(relation, &tup->t_self);
692
693         ReleaseSysCache(tup);
694
695         heap_close(relation, RowExclusiveLock);
696 }
697
698 /*
699  * ALTER TEXT SEARCH DICTIONARY
700  */
701 void
702 AlterTSDictionary(AlterTSDictionaryStmt *stmt)
703 {
704         HeapTuple       tup,
705                                 newtup;
706         Relation        rel;
707         Oid                     dictId;
708         ListCell   *pl;
709         List       *dictoptions;
710         Datum           opt;
711         bool            isnull;
712         Datum           repl_val[Natts_pg_ts_dict];
713         bool            repl_null[Natts_pg_ts_dict];
714         bool            repl_repl[Natts_pg_ts_dict];
715
716         dictId = get_ts_dict_oid(stmt->dictname, false);
717
718         rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
719
720         tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
721
722         if (!HeapTupleIsValid(tup))
723                 elog(ERROR, "cache lookup failed for text search dictionary %u",
724                          dictId);
725
726         /* must be owner */
727         if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
728                 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
729                                            NameListToString(stmt->dictname));
730
731         /* deserialize the existing set of options */
732         opt = SysCacheGetAttr(TSDICTOID, tup,
733                                                   Anum_pg_ts_dict_dictinitoption,
734                                                   &isnull);
735         if (isnull)
736                 dictoptions = NIL;
737         else
738                 dictoptions = deserialize_deflist(opt);
739
740         /*
741          * Modify the options list as per specified changes
742          */
743         foreach(pl, stmt->options)
744         {
745                 DefElem    *defel = (DefElem *) lfirst(pl);
746                 ListCell   *cell;
747                 ListCell   *prev;
748                 ListCell   *next;
749
750                 /*
751                  * Remove any matches ...
752                  */
753                 prev = NULL;
754                 for (cell = list_head(dictoptions); cell; cell = next)
755                 {
756                         DefElem    *oldel = (DefElem *) lfirst(cell);
757
758                         next = lnext(cell);
759                         if (pg_strcasecmp(oldel->defname, defel->defname) == 0)
760                                 dictoptions = list_delete_cell(dictoptions, cell, prev);
761                         else
762                                 prev = cell;
763                 }
764
765                 /*
766                  * and add new value if it's got one
767                  */
768                 if (defel->arg)
769                         dictoptions = lappend(dictoptions, defel);
770         }
771
772         /*
773          * Validate
774          */
775         verify_dictoptions(((Form_pg_ts_dict) GETSTRUCT(tup))->dicttemplate,
776                                            dictoptions);
777
778         /*
779          * Looks good, update
780          */
781         memset(repl_val, 0, sizeof(repl_val));
782         memset(repl_null, false, sizeof(repl_null));
783         memset(repl_repl, false, sizeof(repl_repl));
784
785         if (dictoptions)
786                 repl_val[Anum_pg_ts_dict_dictinitoption - 1] =
787                         PointerGetDatum(serialize_deflist(dictoptions));
788         else
789                 repl_null[Anum_pg_ts_dict_dictinitoption - 1] = true;
790         repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = true;
791
792         newtup = heap_modify_tuple(tup, RelationGetDescr(rel),
793                                                            repl_val, repl_null, repl_repl);
794
795         simple_heap_update(rel, &newtup->t_self, newtup);
796
797         CatalogUpdateIndexes(rel, newtup);
798
799         /*
800          * NOTE: because we only support altering the options, not the template,
801          * there is no need to update dependencies.  This might have to change if
802          * the options ever reference inside-the-database objects.
803          */
804
805         heap_freetuple(newtup);
806         ReleaseSysCache(tup);
807
808         heap_close(rel, RowExclusiveLock);
809 }
810
811 /*
812  * ALTER TEXT SEARCH DICTIONARY OWNER
813  */
814 void
815 AlterTSDictionaryOwner(List *name, Oid newOwnerId)
816 {
817         HeapTuple       tup;
818         Relation        rel;
819         Oid                     dictId;
820         Oid                     namespaceOid;
821         AclResult       aclresult;
822         Form_pg_ts_dict form;
823
824         rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
825
826         dictId = get_ts_dict_oid(name, false);
827
828         tup = SearchSysCacheCopy1(TSDICTOID, ObjectIdGetDatum(dictId));
829
830         if (!HeapTupleIsValid(tup)) /* should not happen */
831                 elog(ERROR, "cache lookup failed for text search dictionary %u",
832                          dictId);
833
834         form = (Form_pg_ts_dict) GETSTRUCT(tup);
835         namespaceOid = form->dictnamespace;
836
837         if (form->dictowner != newOwnerId)
838         {
839                 /* Superusers can always do it */
840                 if (!superuser())
841                 {
842                         /* must be owner */
843                         if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
844                                 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
845                                                            NameListToString(name));
846
847                         /* Must be able to become new owner */
848                         check_is_member_of_role(GetUserId(), newOwnerId);
849
850                         /* New owner must have CREATE privilege on namespace */
851                         aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, ACL_CREATE);
852                         if (aclresult != ACLCHECK_OK)
853                                 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
854                                                            get_namespace_name(namespaceOid));
855                 }
856
857                 form->dictowner = newOwnerId;
858
859                 simple_heap_update(rel, &tup->t_self, tup);
860                 CatalogUpdateIndexes(rel, tup);
861
862                 /* Update owner dependency reference */
863                 changeDependencyOnOwner(TSDictionaryRelationId, HeapTupleGetOid(tup),
864                                                                 newOwnerId);
865         }
866
867         heap_close(rel, NoLock);
868         heap_freetuple(tup);
869 }
870
871 /* ---------------------- TS Template commands -----------------------*/
872
873 /*
874  * lookup a template support function and return its OID (as a Datum)
875  *
876  * attnum is the pg_ts_template column the function will go into
877  */
878 static Datum
879 get_ts_template_func(DefElem *defel, int attnum)
880 {
881         List       *funcName = defGetQualifiedName(defel);
882         Oid                     typeId[4];
883         Oid                     retTypeId;
884         int                     nargs;
885         Oid                     procOid;
886
887         retTypeId = INTERNALOID;
888         typeId[0] = INTERNALOID;
889         typeId[1] = INTERNALOID;
890         typeId[2] = INTERNALOID;
891         typeId[3] = INTERNALOID;
892         switch (attnum)
893         {
894                 case Anum_pg_ts_template_tmplinit:
895                         nargs = 1;
896                         break;
897                 case Anum_pg_ts_template_tmpllexize:
898                         nargs = 4;
899                         break;
900                 default:
901                         /* should not be here */
902                         elog(ERROR, "unrecognized attribute for text search template: %d",
903                                  attnum);
904                         nargs = 0;                      /* keep compiler quiet */
905         }
906
907         procOid = LookupFuncName(funcName, nargs, typeId, false);
908         if (get_func_rettype(procOid) != retTypeId)
909                 ereport(ERROR,
910                                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
911                                  errmsg("function %s should return type %s",
912                                                 func_signature_string(funcName, nargs, NIL, typeId),
913                                                 format_type_be(retTypeId))));
914
915         return ObjectIdGetDatum(procOid);
916 }
917
918 /*
919  * make pg_depend entries for a new pg_ts_template entry
920  */
921 static void
922 makeTSTemplateDependencies(HeapTuple tuple)
923 {
924         Form_pg_ts_template tmpl = (Form_pg_ts_template) GETSTRUCT(tuple);
925         ObjectAddress myself,
926                                 referenced;
927
928         myself.classId = TSTemplateRelationId;
929         myself.objectId = HeapTupleGetOid(tuple);
930         myself.objectSubId = 0;
931
932         /* dependency on namespace */
933         referenced.classId = NamespaceRelationId;
934         referenced.objectId = tmpl->tmplnamespace;
935         referenced.objectSubId = 0;
936         recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
937
938         /* dependency on extension */
939         recordDependencyOnCurrentExtension(&myself, false);
940
941         /* dependencies on functions */
942         referenced.classId = ProcedureRelationId;
943         referenced.objectSubId = 0;
944
945         referenced.objectId = tmpl->tmpllexize;
946         recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
947
948         if (OidIsValid(tmpl->tmplinit))
949         {
950                 referenced.objectId = tmpl->tmplinit;
951                 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
952         }
953 }
954
955 /*
956  * CREATE TEXT SEARCH TEMPLATE
957  */
958 void
959 DefineTSTemplate(List *names, List *parameters)
960 {
961         ListCell   *pl;
962         Relation        tmplRel;
963         HeapTuple       tup;
964         Datum           values[Natts_pg_ts_template];
965         bool            nulls[Natts_pg_ts_template];
966         NameData        dname;
967         int                     i;
968         Oid                     dictOid;
969         Oid                     namespaceoid;
970         char       *tmplname;
971
972         if (!superuser())
973                 ereport(ERROR,
974                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
975                            errmsg("must be superuser to create text search templates")));
976
977         /* Convert list of names to a name and namespace */
978         namespaceoid = QualifiedNameGetCreationNamespace(names, &tmplname);
979
980         for (i = 0; i < Natts_pg_ts_template; i++)
981         {
982                 nulls[i] = false;
983                 values[i] = ObjectIdGetDatum(InvalidOid);
984         }
985
986         namestrcpy(&dname, tmplname);
987         values[Anum_pg_ts_template_tmplname - 1] = NameGetDatum(&dname);
988         values[Anum_pg_ts_template_tmplnamespace - 1] = ObjectIdGetDatum(namespaceoid);
989
990         /*
991          * loop over the definition list and extract the information we need.
992          */
993         foreach(pl, parameters)
994         {
995                 DefElem    *defel = (DefElem *) lfirst(pl);
996
997                 if (pg_strcasecmp(defel->defname, "init") == 0)
998                 {
999                         values[Anum_pg_ts_template_tmplinit - 1] =
1000                                 get_ts_template_func(defel, Anum_pg_ts_template_tmplinit);
1001                         nulls[Anum_pg_ts_template_tmplinit - 1] = false;
1002                 }
1003                 else if (pg_strcasecmp(defel->defname, "lexize") == 0)
1004                 {
1005                         values[Anum_pg_ts_template_tmpllexize - 1] =
1006                                 get_ts_template_func(defel, Anum_pg_ts_template_tmpllexize);
1007                         nulls[Anum_pg_ts_template_tmpllexize - 1] = false;
1008                 }
1009                 else
1010                         ereport(ERROR,
1011                                         (errcode(ERRCODE_SYNTAX_ERROR),
1012                            errmsg("text search template parameter \"%s\" not recognized",
1013                                           defel->defname)));
1014         }
1015
1016         /*
1017          * Validation
1018          */
1019         if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_template_tmpllexize - 1])))
1020                 ereport(ERROR,
1021                                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
1022                                  errmsg("text search template lexize method is required")));
1023
1024         /*
1025          * Looks good, insert
1026          */
1027
1028         tmplRel = heap_open(TSTemplateRelationId, RowExclusiveLock);
1029
1030         tup = heap_form_tuple(tmplRel->rd_att, values, nulls);
1031
1032         dictOid = simple_heap_insert(tmplRel, tup);
1033
1034         CatalogUpdateIndexes(tmplRel, tup);
1035
1036         makeTSTemplateDependencies(tup);
1037
1038         /* Post creation hook for new text search template */
1039         InvokeObjectAccessHook(OAT_POST_CREATE, TSTemplateRelationId, dictOid, 0);
1040
1041         heap_freetuple(tup);
1042
1043         heap_close(tmplRel, RowExclusiveLock);
1044 }
1045
1046 /*
1047  * ALTER TEXT SEARCH TEMPLATE RENAME
1048  */
1049 void
1050 RenameTSTemplate(List *oldname, const char *newname)
1051 {
1052         HeapTuple       tup;
1053         Relation        rel;
1054         Oid                     tmplId;
1055         Oid                     namespaceOid;
1056
1057         if (!superuser())
1058                 ereport(ERROR,
1059                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1060                            errmsg("must be superuser to rename text search templates")));
1061
1062         rel = heap_open(TSTemplateRelationId, RowExclusiveLock);
1063
1064         tmplId = get_ts_template_oid(oldname, false);
1065
1066         tup = SearchSysCacheCopy1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId));
1067
1068         if (!HeapTupleIsValid(tup)) /* should not happen */
1069                 elog(ERROR, "cache lookup failed for text search template %u",
1070                          tmplId);
1071
1072         namespaceOid = ((Form_pg_ts_template) GETSTRUCT(tup))->tmplnamespace;
1073
1074         if (SearchSysCacheExists2(TSTEMPLATENAMENSP,
1075                                                           PointerGetDatum(newname),
1076                                                           ObjectIdGetDatum(namespaceOid)))
1077                 ereport(ERROR,
1078                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
1079                                  errmsg("text search template \"%s\" already exists",
1080                                                 newname)));
1081
1082         namestrcpy(&(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname), newname);
1083         simple_heap_update(rel, &tup->t_self, tup);
1084         CatalogUpdateIndexes(rel, tup);
1085
1086         heap_close(rel, NoLock);
1087         heap_freetuple(tup);
1088 }
1089
1090 /*
1091  * ALTER TEXT SEARCH TEMPLATE any_name SET SCHEMA name
1092  */
1093 void
1094 AlterTSTemplateNamespace(List *name, const char *newschema)
1095 {
1096         Oid                     tmplId,
1097                                 nspOid;
1098         Relation        rel;
1099
1100         rel = heap_open(TSTemplateRelationId, RowExclusiveLock);
1101
1102         tmplId = get_ts_template_oid(name, false);
1103
1104         /* get schema OID */
1105         nspOid = LookupCreationNamespace(newschema);
1106
1107         AlterObjectNamespace(rel, TSTEMPLATEOID, TSTEMPLATENAMENSP,
1108                                                  tmplId, nspOid,
1109                                                  Anum_pg_ts_template_tmplname,
1110                                                  Anum_pg_ts_template_tmplnamespace,
1111                                                  -1, -1);
1112
1113         heap_close(rel, RowExclusiveLock);
1114 }
1115
1116 Oid
1117 AlterTSTemplateNamespace_oid(Oid tmplId, Oid newNspOid)
1118 {
1119         Oid                     oldNspOid;
1120         Relation        rel;
1121
1122         rel = heap_open(TSTemplateRelationId, RowExclusiveLock);
1123
1124         oldNspOid =
1125                 AlterObjectNamespace(rel, TSTEMPLATEOID, TSTEMPLATENAMENSP,
1126                                                          tmplId, newNspOid,
1127                                                          Anum_pg_ts_template_tmplname,
1128                                                          Anum_pg_ts_template_tmplnamespace,
1129                                                          -1, -1);
1130
1131         heap_close(rel, RowExclusiveLock);
1132
1133         return oldNspOid;
1134 }
1135
1136 /*
1137  * Guts of TS template deletion.
1138  */
1139 void
1140 RemoveTSTemplateById(Oid tmplId)
1141 {
1142         Relation        relation;
1143         HeapTuple       tup;
1144
1145         relation = heap_open(TSTemplateRelationId, RowExclusiveLock);
1146
1147         tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId));
1148
1149         if (!HeapTupleIsValid(tup))
1150                 elog(ERROR, "cache lookup failed for text search template %u",
1151                          tmplId);
1152
1153         simple_heap_delete(relation, &tup->t_self);
1154
1155         ReleaseSysCache(tup);
1156
1157         heap_close(relation, RowExclusiveLock);
1158 }
1159
1160 /* ---------------------- TS Configuration commands -----------------------*/
1161
1162 /*
1163  * Finds syscache tuple of configuration.
1164  * Returns NULL if no such cfg.
1165  */
1166 static HeapTuple
1167 GetTSConfigTuple(List *names)
1168 {
1169         HeapTuple       tup;
1170         Oid                     cfgId;
1171
1172         cfgId = get_ts_config_oid(names, true);
1173         if (!OidIsValid(cfgId))
1174                 return NULL;
1175
1176         tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
1177
1178         if (!HeapTupleIsValid(tup)) /* should not happen */
1179                 elog(ERROR, "cache lookup failed for text search configuration %u",
1180                          cfgId);
1181
1182         return tup;
1183 }
1184
1185 /*
1186  * make pg_depend entries for a new or updated pg_ts_config entry
1187  *
1188  * Pass opened pg_ts_config_map relation if there might be any config map
1189  * entries for the config.
1190  */
1191 static void
1192 makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
1193                                                           Relation mapRel)
1194 {
1195         Form_pg_ts_config cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
1196         ObjectAddresses *addrs;
1197         ObjectAddress myself,
1198                                 referenced;
1199
1200         myself.classId = TSConfigRelationId;
1201         myself.objectId = HeapTupleGetOid(tuple);
1202         myself.objectSubId = 0;
1203
1204         /* for ALTER case, first flush old dependencies, except extension deps */
1205         if (removeOld)
1206         {
1207                 deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
1208                 deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0);
1209         }
1210
1211         /*
1212          * We use an ObjectAddresses list to remove possible duplicate
1213          * dependencies from the config map info.  The pg_ts_config items
1214          * shouldn't be duplicates, but might as well fold them all into one call.
1215          */
1216         addrs = new_object_addresses();
1217
1218         /* dependency on namespace */
1219         referenced.classId = NamespaceRelationId;
1220         referenced.objectId = cfg->cfgnamespace;
1221         referenced.objectSubId = 0;
1222         add_exact_object_address(&referenced, addrs);
1223
1224         /* dependency on owner */
1225         recordDependencyOnOwner(myself.classId, myself.objectId, cfg->cfgowner);
1226
1227         /* dependency on extension */
1228         recordDependencyOnCurrentExtension(&myself, removeOld);
1229
1230         /* dependency on parser */
1231         referenced.classId = TSParserRelationId;
1232         referenced.objectId = cfg->cfgparser;
1233         referenced.objectSubId = 0;
1234         add_exact_object_address(&referenced, addrs);
1235
1236         /* dependencies on dictionaries listed in config map */
1237         if (mapRel)
1238         {
1239                 ScanKeyData skey;
1240                 SysScanDesc scan;
1241                 HeapTuple       maptup;
1242
1243                 /* CCI to ensure we can see effects of caller's changes */
1244                 CommandCounterIncrement();
1245
1246                 ScanKeyInit(&skey,
1247                                         Anum_pg_ts_config_map_mapcfg,
1248                                         BTEqualStrategyNumber, F_OIDEQ,
1249                                         ObjectIdGetDatum(myself.objectId));
1250
1251                 scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
1252                                                                   SnapshotNow, 1, &skey);
1253
1254                 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1255                 {
1256                         Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
1257
1258                         referenced.classId = TSDictionaryRelationId;
1259                         referenced.objectId = cfgmap->mapdict;
1260                         referenced.objectSubId = 0;
1261                         add_exact_object_address(&referenced, addrs);
1262                 }
1263
1264                 systable_endscan(scan);
1265         }
1266
1267         /* Record 'em (this includes duplicate elimination) */
1268         record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
1269
1270         free_object_addresses(addrs);
1271 }
1272
1273 /*
1274  * CREATE TEXT SEARCH CONFIGURATION
1275  */
1276 void
1277 DefineTSConfiguration(List *names, List *parameters)
1278 {
1279         Relation        cfgRel;
1280         Relation        mapRel = NULL;
1281         HeapTuple       tup;
1282         Datum           values[Natts_pg_ts_config];
1283         bool            nulls[Natts_pg_ts_config];
1284         AclResult       aclresult;
1285         Oid                     namespaceoid;
1286         char       *cfgname;
1287         NameData        cname;
1288         Oid                     sourceOid = InvalidOid;
1289         Oid                     prsOid = InvalidOid;
1290         Oid                     cfgOid;
1291         ListCell   *pl;
1292
1293         /* Convert list of names to a name and namespace */
1294         namespaceoid = QualifiedNameGetCreationNamespace(names, &cfgname);
1295
1296         /* Check we have creation rights in target namespace */
1297         aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
1298         if (aclresult != ACLCHECK_OK)
1299                 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
1300                                            get_namespace_name(namespaceoid));
1301
1302         /*
1303          * loop over the definition list and extract the information we need.
1304          */
1305         foreach(pl, parameters)
1306         {
1307                 DefElem    *defel = (DefElem *) lfirst(pl);
1308
1309                 if (pg_strcasecmp(defel->defname, "parser") == 0)
1310                         prsOid = get_ts_parser_oid(defGetQualifiedName(defel), false);
1311                 else if (pg_strcasecmp(defel->defname, "copy") == 0)
1312                         sourceOid = get_ts_config_oid(defGetQualifiedName(defel), false);
1313                 else
1314                         ereport(ERROR,
1315                                         (errcode(ERRCODE_SYNTAX_ERROR),
1316                                          errmsg("text search configuration parameter \"%s\" not recognized",
1317                                                         defel->defname)));
1318         }
1319
1320         if (OidIsValid(sourceOid) && OidIsValid(prsOid))
1321                 ereport(ERROR,
1322                                 (errcode(ERRCODE_SYNTAX_ERROR),
1323                                  errmsg("cannot specify both PARSER and COPY options")));
1324
1325         /*
1326          * Look up source config if given.
1327          */
1328         if (OidIsValid(sourceOid))
1329         {
1330                 Form_pg_ts_config cfg;
1331
1332                 tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(sourceOid));
1333                 if (!HeapTupleIsValid(tup))
1334                         elog(ERROR, "cache lookup failed for text search configuration %u",
1335                                  sourceOid);
1336
1337                 cfg = (Form_pg_ts_config) GETSTRUCT(tup);
1338
1339                 /* use source's parser */
1340                 prsOid = cfg->cfgparser;
1341
1342                 ReleaseSysCache(tup);
1343         }
1344
1345         /*
1346          * Validation
1347          */
1348         if (!OidIsValid(prsOid))
1349                 ereport(ERROR,
1350                                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
1351                                  errmsg("text search parser is required")));
1352
1353         /*
1354          * Looks good, build tuple and insert
1355          */
1356         memset(values, 0, sizeof(values));
1357         memset(nulls, false, sizeof(nulls));
1358
1359         namestrcpy(&cname, cfgname);
1360         values[Anum_pg_ts_config_cfgname - 1] = NameGetDatum(&cname);
1361         values[Anum_pg_ts_config_cfgnamespace - 1] = ObjectIdGetDatum(namespaceoid);
1362         values[Anum_pg_ts_config_cfgowner - 1] = ObjectIdGetDatum(GetUserId());
1363         values[Anum_pg_ts_config_cfgparser - 1] = ObjectIdGetDatum(prsOid);
1364
1365         cfgRel = heap_open(TSConfigRelationId, RowExclusiveLock);
1366
1367         tup = heap_form_tuple(cfgRel->rd_att, values, nulls);
1368
1369         cfgOid = simple_heap_insert(cfgRel, tup);
1370
1371         CatalogUpdateIndexes(cfgRel, tup);
1372
1373         if (OidIsValid(sourceOid))
1374         {
1375                 /*
1376                  * Copy token-dicts map from source config
1377                  */
1378                 ScanKeyData skey;
1379                 SysScanDesc scan;
1380                 HeapTuple       maptup;
1381
1382                 mapRel = heap_open(TSConfigMapRelationId, RowExclusiveLock);
1383
1384                 ScanKeyInit(&skey,
1385                                         Anum_pg_ts_config_map_mapcfg,
1386                                         BTEqualStrategyNumber, F_OIDEQ,
1387                                         ObjectIdGetDatum(sourceOid));
1388
1389                 scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
1390                                                                   SnapshotNow, 1, &skey);
1391
1392                 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1393                 {
1394                         Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
1395                         HeapTuple       newmaptup;
1396                         Datum           mapvalues[Natts_pg_ts_config_map];
1397                         bool            mapnulls[Natts_pg_ts_config_map];
1398
1399                         memset(mapvalues, 0, sizeof(mapvalues));
1400                         memset(mapnulls, false, sizeof(mapnulls));
1401
1402                         mapvalues[Anum_pg_ts_config_map_mapcfg - 1] = cfgOid;
1403                         mapvalues[Anum_pg_ts_config_map_maptokentype - 1] = cfgmap->maptokentype;
1404                         mapvalues[Anum_pg_ts_config_map_mapseqno - 1] = cfgmap->mapseqno;
1405                         mapvalues[Anum_pg_ts_config_map_mapdict - 1] = cfgmap->mapdict;
1406
1407                         newmaptup = heap_form_tuple(mapRel->rd_att, mapvalues, mapnulls);
1408
1409                         simple_heap_insert(mapRel, newmaptup);
1410
1411                         CatalogUpdateIndexes(mapRel, newmaptup);
1412
1413                         heap_freetuple(newmaptup);
1414                 }
1415
1416                 systable_endscan(scan);
1417         }
1418
1419         makeConfigurationDependencies(tup, false, mapRel);
1420
1421         /* Post creation hook for new text search configuration */
1422         InvokeObjectAccessHook(OAT_POST_CREATE, TSConfigRelationId, cfgOid, 0);
1423
1424         heap_freetuple(tup);
1425
1426         if (mapRel)
1427                 heap_close(mapRel, RowExclusiveLock);
1428         heap_close(cfgRel, RowExclusiveLock);
1429 }
1430
1431 /*
1432  * ALTER TEXT SEARCH CONFIGURATION RENAME
1433  */
1434 void
1435 RenameTSConfiguration(List *oldname, const char *newname)
1436 {
1437         HeapTuple       tup;
1438         Relation        rel;
1439         Oid                     cfgId;
1440         AclResult       aclresult;
1441         Oid                     namespaceOid;
1442
1443         rel = heap_open(TSConfigRelationId, RowExclusiveLock);
1444
1445         cfgId = get_ts_config_oid(oldname, false);
1446
1447         tup = SearchSysCacheCopy1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
1448
1449         if (!HeapTupleIsValid(tup)) /* should not happen */
1450                 elog(ERROR, "cache lookup failed for text search configuration %u",
1451                          cfgId);
1452
1453         namespaceOid = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgnamespace;
1454
1455         if (SearchSysCacheExists2(TSCONFIGNAMENSP,
1456                                                           PointerGetDatum(newname),
1457                                                           ObjectIdGetDatum(namespaceOid)))
1458                 ereport(ERROR,
1459                                 (errcode(ERRCODE_DUPLICATE_OBJECT),
1460                                  errmsg("text search configuration \"%s\" already exists",
1461                                                 newname)));
1462
1463         /* must be owner */
1464         if (!pg_ts_config_ownercheck(cfgId, GetUserId()))
1465                 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
1466                                            NameListToString(oldname));
1467
1468         /* must have CREATE privilege on namespace */
1469         aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);
1470         aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
1471                                    get_namespace_name(namespaceOid));
1472
1473         namestrcpy(&(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname), newname);
1474         simple_heap_update(rel, &tup->t_self, tup);
1475         CatalogUpdateIndexes(rel, tup);
1476
1477         heap_close(rel, NoLock);
1478         heap_freetuple(tup);
1479 }
1480
1481 /*
1482  * ALTER TEXT SEARCH CONFIGURATION any_name SET SCHEMA name
1483  */
1484 void
1485 AlterTSConfigurationNamespace(List *name, const char *newschema)
1486 {
1487         Oid                     cfgId,
1488                                 nspOid;
1489         Relation        rel;
1490
1491         rel = heap_open(TSConfigRelationId, RowExclusiveLock);
1492
1493         cfgId = get_ts_config_oid(name, false);
1494
1495         /* get schema OID */
1496         nspOid = LookupCreationNamespace(newschema);
1497
1498         AlterObjectNamespace(rel, TSCONFIGOID, TSCONFIGNAMENSP,
1499                                                  cfgId, nspOid,
1500                                                  Anum_pg_ts_config_cfgname,
1501                                                  Anum_pg_ts_config_cfgnamespace,
1502                                                  Anum_pg_ts_config_cfgowner,
1503                                                  ACL_KIND_TSCONFIGURATION);
1504
1505         heap_close(rel, RowExclusiveLock);
1506 }
1507
1508 Oid
1509 AlterTSConfigurationNamespace_oid(Oid cfgId, Oid newNspOid)
1510 {
1511         Oid                     oldNspOid;
1512         Relation        rel;
1513
1514         rel = heap_open(TSConfigRelationId, RowExclusiveLock);
1515
1516         oldNspOid =
1517                 AlterObjectNamespace(rel, TSCONFIGOID, TSCONFIGNAMENSP,
1518                                                          cfgId, newNspOid,
1519                                                          Anum_pg_ts_config_cfgname,
1520                                                          Anum_pg_ts_config_cfgnamespace,
1521                                                          Anum_pg_ts_config_cfgowner,
1522                                                          ACL_KIND_TSCONFIGURATION);
1523
1524         heap_close(rel, RowExclusiveLock);
1525
1526         return oldNspOid;
1527 }
1528
1529 /*
1530  * Guts of TS configuration deletion.
1531  */
1532 void
1533 RemoveTSConfigurationById(Oid cfgId)
1534 {
1535         Relation        relCfg,
1536                                 relMap;
1537         HeapTuple       tup;
1538         ScanKeyData skey;
1539         SysScanDesc scan;
1540
1541         /* Remove the pg_ts_config entry */
1542         relCfg = heap_open(TSConfigRelationId, RowExclusiveLock);
1543
1544         tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
1545
1546         if (!HeapTupleIsValid(tup))
1547                 elog(ERROR, "cache lookup failed for text search dictionary %u",
1548                          cfgId);
1549
1550         simple_heap_delete(relCfg, &tup->t_self);
1551
1552         ReleaseSysCache(tup);
1553
1554         heap_close(relCfg, RowExclusiveLock);
1555
1556         /* Remove any pg_ts_config_map entries */
1557         relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock);
1558
1559         ScanKeyInit(&skey,
1560                                 Anum_pg_ts_config_map_mapcfg,
1561                                 BTEqualStrategyNumber, F_OIDEQ,
1562                                 ObjectIdGetDatum(cfgId));
1563
1564         scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1565                                                           SnapshotNow, 1, &skey);
1566
1567         while (HeapTupleIsValid((tup = systable_getnext(scan))))
1568         {
1569                 simple_heap_delete(relMap, &tup->t_self);
1570         }
1571
1572         systable_endscan(scan);
1573
1574         heap_close(relMap, RowExclusiveLock);
1575 }
1576
1577 /*
1578  * ALTER TEXT SEARCH CONFIGURATION OWNER
1579  */
1580 void
1581 AlterTSConfigurationOwner(List *name, Oid newOwnerId)
1582 {
1583         HeapTuple       tup;
1584         Relation        rel;
1585         Oid                     cfgId;
1586         AclResult       aclresult;
1587         Oid                     namespaceOid;
1588         Form_pg_ts_config form;
1589
1590         rel = heap_open(TSConfigRelationId, RowExclusiveLock);
1591
1592         cfgId = get_ts_config_oid(name, false);
1593
1594         tup = SearchSysCacheCopy1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
1595
1596         if (!HeapTupleIsValid(tup)) /* should not happen */
1597                 elog(ERROR, "cache lookup failed for text search configuration %u",
1598                          cfgId);
1599
1600         form = (Form_pg_ts_config) GETSTRUCT(tup);
1601         namespaceOid = form->cfgnamespace;
1602
1603         if (form->cfgowner != newOwnerId)
1604         {
1605                 /* Superusers can always do it */
1606                 if (!superuser())
1607                 {
1608                         /* must be owner */
1609                         if (!pg_ts_config_ownercheck(cfgId, GetUserId()))
1610                                 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
1611                                                            NameListToString(name));
1612
1613                         /* Must be able to become new owner */
1614                         check_is_member_of_role(GetUserId(), newOwnerId);
1615
1616                         /* New owner must have CREATE privilege on namespace */
1617                         aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, ACL_CREATE);
1618                         if (aclresult != ACLCHECK_OK)
1619                                 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
1620                                                            get_namespace_name(namespaceOid));
1621                 }
1622
1623                 form->cfgowner = newOwnerId;
1624
1625                 simple_heap_update(rel, &tup->t_self, tup);
1626                 CatalogUpdateIndexes(rel, tup);
1627
1628                 /* Update owner dependency reference */
1629                 changeDependencyOnOwner(TSConfigRelationId, HeapTupleGetOid(tup),
1630                                                                 newOwnerId);
1631         }
1632
1633         heap_close(rel, NoLock);
1634         heap_freetuple(tup);
1635 }
1636
1637 /*
1638  * ALTER TEXT SEARCH CONFIGURATION - main entry point
1639  */
1640 void
1641 AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
1642 {
1643         HeapTuple       tup;
1644         Relation        relMap;
1645
1646         /* Find the configuration */
1647         tup = GetTSConfigTuple(stmt->cfgname);
1648         if (!HeapTupleIsValid(tup))
1649                 ereport(ERROR,
1650                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
1651                                  errmsg("text search configuration \"%s\" does not exist",
1652                                                 NameListToString(stmt->cfgname))));
1653
1654         /* must be owner */
1655         if (!pg_ts_config_ownercheck(HeapTupleGetOid(tup), GetUserId()))
1656                 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
1657                                            NameListToString(stmt->cfgname));
1658
1659         relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock);
1660
1661         /* Add or drop mappings */
1662         if (stmt->dicts)
1663                 MakeConfigurationMapping(stmt, tup, relMap);
1664         else if (stmt->tokentype)
1665                 DropConfigurationMapping(stmt, tup, relMap);
1666
1667         /* Update dependencies */
1668         makeConfigurationDependencies(tup, true, relMap);
1669
1670         heap_close(relMap, RowExclusiveLock);
1671
1672         ReleaseSysCache(tup);
1673 }
1674
1675 /*
1676  * Translate a list of token type names to an array of token type numbers
1677  */
1678 static int *
1679 getTokenTypes(Oid prsId, List *tokennames)
1680 {
1681         TSParserCacheEntry *prs = lookup_ts_parser_cache(prsId);
1682         LexDescr   *list;
1683         int                *res,
1684                                 i,
1685                                 ntoken;
1686         ListCell   *tn;
1687
1688         ntoken = list_length(tokennames);
1689         if (ntoken == 0)
1690                 return NULL;
1691         res = (int *) palloc(sizeof(int) * ntoken);
1692
1693         if (!OidIsValid(prs->lextypeOid))
1694                 elog(ERROR, "method lextype isn't defined for text search parser %u",
1695                          prsId);
1696
1697         /* lextype takes one dummy argument */
1698         list = (LexDescr *) DatumGetPointer(OidFunctionCall1(prs->lextypeOid,
1699                                                                                                                  (Datum) 0));
1700
1701         i = 0;
1702         foreach(tn, tokennames)
1703         {
1704                 Value      *val = (Value *) lfirst(tn);
1705                 bool            found = false;
1706                 int                     j;
1707
1708                 j = 0;
1709                 while (list && list[j].lexid)
1710                 {
1711                         /* XXX should we use pg_strcasecmp here? */
1712                         if (strcmp(strVal(val), list[j].alias) == 0)
1713                         {
1714                                 res[i] = list[j].lexid;
1715                                 found = true;
1716                                 break;
1717                         }
1718                         j++;
1719                 }
1720                 if (!found)
1721                         ereport(ERROR,
1722                                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1723                                          errmsg("token type \"%s\" does not exist",
1724                                                         strVal(val))));
1725                 i++;
1726         }
1727
1728         return res;
1729 }
1730
1731 /*
1732  * ALTER TEXT SEARCH CONFIGURATION ADD/ALTER MAPPING
1733  */
1734 static void
1735 MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
1736                                                  HeapTuple tup, Relation relMap)
1737 {
1738         Oid                     cfgId = HeapTupleGetOid(tup);
1739         ScanKeyData skey[2];
1740         SysScanDesc scan;
1741         HeapTuple       maptup;
1742         int                     i;
1743         int                     j;
1744         Oid                     prsId;
1745         int                *tokens,
1746                                 ntoken;
1747         Oid                *dictIds;
1748         int                     ndict;
1749         ListCell   *c;
1750
1751         prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser;
1752
1753         tokens = getTokenTypes(prsId, stmt->tokentype);
1754         ntoken = list_length(stmt->tokentype);
1755
1756         if (stmt->override)
1757         {
1758                 /*
1759                  * delete maps for tokens if they exist and command was ALTER
1760                  */
1761                 for (i = 0; i < ntoken; i++)
1762                 {
1763                         ScanKeyInit(&skey[0],
1764                                                 Anum_pg_ts_config_map_mapcfg,
1765                                                 BTEqualStrategyNumber, F_OIDEQ,
1766                                                 ObjectIdGetDatum(cfgId));
1767                         ScanKeyInit(&skey[1],
1768                                                 Anum_pg_ts_config_map_maptokentype,
1769                                                 BTEqualStrategyNumber, F_INT4EQ,
1770                                                 Int32GetDatum(tokens[i]));
1771
1772                         scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1773                                                                           SnapshotNow, 2, skey);
1774
1775                         while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1776                         {
1777                                 simple_heap_delete(relMap, &maptup->t_self);
1778                         }
1779
1780                         systable_endscan(scan);
1781                 }
1782         }
1783
1784         /*
1785          * Convert list of dictionary names to array of dict OIDs
1786          */
1787         ndict = list_length(stmt->dicts);
1788         dictIds = (Oid *) palloc(sizeof(Oid) * ndict);
1789         i = 0;
1790         foreach(c, stmt->dicts)
1791         {
1792                 List       *names = (List *) lfirst(c);
1793
1794                 dictIds[i] = get_ts_dict_oid(names, false);
1795                 i++;
1796         }
1797
1798         if (stmt->replace)
1799         {
1800                 /*
1801                  * Replace a specific dictionary in existing entries
1802                  */
1803                 Oid                     dictOld = dictIds[0],
1804                                         dictNew = dictIds[1];
1805
1806                 ScanKeyInit(&skey[0],
1807                                         Anum_pg_ts_config_map_mapcfg,
1808                                         BTEqualStrategyNumber, F_OIDEQ,
1809                                         ObjectIdGetDatum(cfgId));
1810
1811                 scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1812                                                                   SnapshotNow, 1, skey);
1813
1814                 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1815                 {
1816                         Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
1817
1818                         /*
1819                          * check if it's one of target token types
1820                          */
1821                         if (tokens)
1822                         {
1823                                 bool            tokmatch = false;
1824
1825                                 for (j = 0; j < ntoken; j++)
1826                                 {
1827                                         if (cfgmap->maptokentype == tokens[j])
1828                                         {
1829                                                 tokmatch = true;
1830                                                 break;
1831                                         }
1832                                 }
1833                                 if (!tokmatch)
1834                                         continue;
1835                         }
1836
1837                         /*
1838                          * replace dictionary if match
1839                          */
1840                         if (cfgmap->mapdict == dictOld)
1841                         {
1842                                 Datum           repl_val[Natts_pg_ts_config_map];
1843                                 bool            repl_null[Natts_pg_ts_config_map];
1844                                 bool            repl_repl[Natts_pg_ts_config_map];
1845                                 HeapTuple       newtup;
1846
1847                                 memset(repl_val, 0, sizeof(repl_val));
1848                                 memset(repl_null, false, sizeof(repl_null));
1849                                 memset(repl_repl, false, sizeof(repl_repl));
1850
1851                                 repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew);
1852                                 repl_repl[Anum_pg_ts_config_map_mapdict - 1] = true;
1853
1854                                 newtup = heap_modify_tuple(maptup,
1855                                                                                    RelationGetDescr(relMap),
1856                                                                                    repl_val, repl_null, repl_repl);
1857                                 simple_heap_update(relMap, &newtup->t_self, newtup);
1858
1859                                 CatalogUpdateIndexes(relMap, newtup);
1860                         }
1861                 }
1862
1863                 systable_endscan(scan);
1864         }
1865         else
1866         {
1867                 /*
1868                  * Insertion of new entries
1869                  */
1870                 for (i = 0; i < ntoken; i++)
1871                 {
1872                         for (j = 0; j < ndict; j++)
1873                         {
1874                                 Datum           values[Natts_pg_ts_config_map];
1875                                 bool            nulls[Natts_pg_ts_config_map];
1876
1877                                 memset(nulls, false, sizeof(nulls));
1878                                 values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId);
1879                                 values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(tokens[i]);
1880                                 values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1);
1881                                 values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]);
1882
1883                                 tup = heap_form_tuple(relMap->rd_att, values, nulls);
1884                                 simple_heap_insert(relMap, tup);
1885                                 CatalogUpdateIndexes(relMap, tup);
1886
1887                                 heap_freetuple(tup);
1888                         }
1889                 }
1890         }
1891 }
1892
1893 /*
1894  * ALTER TEXT SEARCH CONFIGURATION DROP MAPPING
1895  */
1896 static void
1897 DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
1898                                                  HeapTuple tup, Relation relMap)
1899 {
1900         Oid                     cfgId = HeapTupleGetOid(tup);
1901         ScanKeyData skey[2];
1902         SysScanDesc scan;
1903         HeapTuple       maptup;
1904         int                     i;
1905         Oid                     prsId;
1906         int                *tokens;
1907         ListCell   *c;
1908
1909         prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser;
1910
1911         tokens = getTokenTypes(prsId, stmt->tokentype);
1912
1913         i = 0;
1914         foreach(c, stmt->tokentype)
1915         {
1916                 Value      *val = (Value *) lfirst(c);
1917                 bool            found = false;
1918
1919                 ScanKeyInit(&skey[0],
1920                                         Anum_pg_ts_config_map_mapcfg,
1921                                         BTEqualStrategyNumber, F_OIDEQ,
1922                                         ObjectIdGetDatum(cfgId));
1923                 ScanKeyInit(&skey[1],
1924                                         Anum_pg_ts_config_map_maptokentype,
1925                                         BTEqualStrategyNumber, F_INT4EQ,
1926                                         Int32GetDatum(tokens[i]));
1927
1928                 scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1929                                                                   SnapshotNow, 2, skey);
1930
1931                 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1932                 {
1933                         simple_heap_delete(relMap, &maptup->t_self);
1934                         found = true;
1935                 }
1936
1937                 systable_endscan(scan);
1938
1939                 if (!found)
1940                 {
1941                         if (!stmt->missing_ok)
1942                         {
1943                                 ereport(ERROR,
1944                                                 (errcode(ERRCODE_UNDEFINED_OBJECT),
1945                                            errmsg("mapping for token type \"%s\" does not exist",
1946                                                           strVal(val))));
1947                         }
1948                         else
1949                         {
1950                                 ereport(NOTICE,
1951                                                 (errmsg("mapping for token type \"%s\" does not exist, skipping",
1952                                                                 strVal(val))));
1953                         }
1954                 }
1955
1956                 i++;
1957         }
1958 }
1959
1960
1961 /*
1962  * Serialize dictionary options, producing a TEXT datum from a List of DefElem
1963  *
1964  * This is used to form the value stored in pg_ts_dict.dictinitoption.
1965  * For the convenience of pg_dump, the output is formatted exactly as it
1966  * would need to appear in CREATE TEXT SEARCH DICTIONARY to reproduce the
1967  * same options.
1968  *
1969  * Note that we assume that only the textual representation of an option's
1970  * value is interesting --- hence, non-string DefElems get forced to strings.
1971  */
1972 text *
1973 serialize_deflist(List *deflist)
1974 {
1975         text       *result;
1976         StringInfoData buf;
1977         ListCell   *l;
1978
1979         initStringInfo(&buf);
1980
1981         foreach(l, deflist)
1982         {
1983                 DefElem    *defel = (DefElem *) lfirst(l);
1984                 char       *val = defGetString(defel);
1985
1986                 appendStringInfo(&buf, "%s = ",
1987                                                  quote_identifier(defel->defname));
1988                 /* If backslashes appear, force E syntax to determine their handling */
1989                 if (strchr(val, '\\'))
1990                         appendStringInfoChar(&buf, ESCAPE_STRING_SYNTAX);
1991                 appendStringInfoChar(&buf, '\'');
1992                 while (*val)
1993                 {
1994                         char            ch = *val++;
1995
1996                         if (SQL_STR_DOUBLE(ch, true))
1997                                 appendStringInfoChar(&buf, ch);
1998                         appendStringInfoChar(&buf, ch);
1999                 }
2000                 appendStringInfoChar(&buf, '\'');
2001                 if (lnext(l) != NULL)
2002                         appendStringInfo(&buf, ", ");
2003         }
2004
2005         result = cstring_to_text_with_len(buf.data, buf.len);
2006         pfree(buf.data);
2007         return result;
2008 }
2009
2010 /*
2011  * Deserialize dictionary options, reconstructing a List of DefElem from TEXT
2012  *
2013  * This is also used for prsheadline options, so for backward compatibility
2014  * we need to accept a few things serialize_deflist() will never emit:
2015  * in particular, unquoted and double-quoted values.
2016  */
2017 List *
2018 deserialize_deflist(Datum txt)
2019 {
2020         text       *in = DatumGetTextP(txt);            /* in case it's toasted */
2021         List       *result = NIL;
2022         int                     len = VARSIZE(in) - VARHDRSZ;
2023         char       *ptr,
2024                            *endptr,
2025                            *workspace,
2026                            *wsptr = NULL,
2027                            *startvalue = NULL;
2028         typedef enum
2029         {
2030                 CS_WAITKEY,
2031                 CS_INKEY,
2032                 CS_INQKEY,
2033                 CS_WAITEQ,
2034                 CS_WAITVALUE,
2035                 CS_INSQVALUE,
2036                 CS_INDQVALUE,
2037                 CS_INWVALUE
2038         } ds_state;
2039         ds_state        state = CS_WAITKEY;
2040
2041         workspace = (char *) palloc(len + 1);           /* certainly enough room */
2042         ptr = VARDATA(in);
2043         endptr = ptr + len;
2044         for (; ptr < endptr; ptr++)
2045         {
2046                 switch (state)
2047                 {
2048                         case CS_WAITKEY:
2049                                 if (isspace((unsigned char) *ptr) || *ptr == ',')
2050                                         continue;
2051                                 if (*ptr == '"')
2052                                 {
2053                                         wsptr = workspace;
2054                                         state = CS_INQKEY;
2055                                 }
2056                                 else
2057                                 {
2058                                         wsptr = workspace;
2059                                         *wsptr++ = *ptr;
2060                                         state = CS_INKEY;
2061                                 }
2062                                 break;
2063                         case CS_INKEY:
2064                                 if (isspace((unsigned char) *ptr))
2065                                 {
2066                                         *wsptr++ = '\0';
2067                                         state = CS_WAITEQ;
2068                                 }
2069                                 else if (*ptr == '=')
2070                                 {
2071                                         *wsptr++ = '\0';
2072                                         state = CS_WAITVALUE;
2073                                 }
2074                                 else
2075                                 {
2076                                         *wsptr++ = *ptr;
2077                                 }
2078                                 break;
2079                         case CS_INQKEY:
2080                                 if (*ptr == '"')
2081                                 {
2082                                         if (ptr + 1 < endptr && ptr[1] == '"')
2083                                         {
2084                                                 /* copy only one of the two quotes */
2085                                                 *wsptr++ = *ptr++;
2086                                         }
2087                                         else
2088                                         {
2089                                                 *wsptr++ = '\0';
2090                                                 state = CS_WAITEQ;
2091                                         }
2092                                 }
2093                                 else
2094                                 {
2095                                         *wsptr++ = *ptr;
2096                                 }
2097                                 break;
2098                         case CS_WAITEQ:
2099                                 if (*ptr == '=')
2100                                         state = CS_WAITVALUE;
2101                                 else if (!isspace((unsigned char) *ptr))
2102                                         ereport(ERROR,
2103                                                         (errcode(ERRCODE_SYNTAX_ERROR),
2104                                                          errmsg("invalid parameter list format: \"%s\"",
2105                                                                         text_to_cstring(in))));
2106                                 break;
2107                         case CS_WAITVALUE:
2108                                 if (*ptr == '\'')
2109                                 {
2110                                         startvalue = wsptr;
2111                                         state = CS_INSQVALUE;
2112                                 }
2113                                 else if (*ptr == 'E' && ptr + 1 < endptr && ptr[1] == '\'')
2114                                 {
2115                                         ptr++;
2116                                         startvalue = wsptr;
2117                                         state = CS_INSQVALUE;
2118                                 }
2119                                 else if (*ptr == '"')
2120                                 {
2121                                         startvalue = wsptr;
2122                                         state = CS_INDQVALUE;
2123                                 }
2124                                 else if (!isspace((unsigned char) *ptr))
2125                                 {
2126                                         startvalue = wsptr;
2127                                         *wsptr++ = *ptr;
2128                                         state = CS_INWVALUE;
2129                                 }
2130                                 break;
2131                         case CS_INSQVALUE:
2132                                 if (*ptr == '\'')
2133                                 {
2134                                         if (ptr + 1 < endptr && ptr[1] == '\'')
2135                                         {
2136                                                 /* copy only one of the two quotes */
2137                                                 *wsptr++ = *ptr++;
2138                                         }
2139                                         else
2140                                         {
2141                                                 *wsptr++ = '\0';
2142                                                 result = lappend(result,
2143                                                                                  makeDefElem(pstrdup(workspace),
2144                                                                   (Node *) makeString(pstrdup(startvalue))));
2145                                                 state = CS_WAITKEY;
2146                                         }
2147                                 }
2148                                 else if (*ptr == '\\')
2149                                 {
2150                                         if (ptr + 1 < endptr && ptr[1] == '\\')
2151                                         {
2152                                                 /* copy only one of the two backslashes */
2153                                                 *wsptr++ = *ptr++;
2154                                         }
2155                                         else
2156                                                 *wsptr++ = *ptr;
2157                                 }
2158                                 else
2159                                 {
2160                                         *wsptr++ = *ptr;
2161                                 }
2162                                 break;
2163                         case CS_INDQVALUE:
2164                                 if (*ptr == '"')
2165                                 {
2166                                         if (ptr + 1 < endptr && ptr[1] == '"')
2167                                         {
2168                                                 /* copy only one of the two quotes */
2169                                                 *wsptr++ = *ptr++;
2170                                         }
2171                                         else
2172                                         {
2173                                                 *wsptr++ = '\0';
2174                                                 result = lappend(result,
2175                                                                                  makeDefElem(pstrdup(workspace),
2176                                                                   (Node *) makeString(pstrdup(startvalue))));
2177                                                 state = CS_WAITKEY;
2178                                         }
2179                                 }
2180                                 else
2181                                 {
2182                                         *wsptr++ = *ptr;
2183                                 }
2184                                 break;
2185                         case CS_INWVALUE:
2186                                 if (*ptr == ',' || isspace((unsigned char) *ptr))
2187                                 {
2188                                         *wsptr++ = '\0';
2189                                         result = lappend(result,
2190                                                                          makeDefElem(pstrdup(workspace),
2191                                                                   (Node *) makeString(pstrdup(startvalue))));
2192                                         state = CS_WAITKEY;
2193                                 }
2194                                 else
2195                                 {
2196                                         *wsptr++ = *ptr;
2197                                 }
2198                                 break;
2199                         default:
2200                                 elog(ERROR, "unrecognized deserialize_deflist state: %d",
2201                                          state);
2202                 }
2203         }
2204
2205         if (state == CS_INWVALUE)
2206         {
2207                 *wsptr++ = '\0';
2208                 result = lappend(result,
2209                                                  makeDefElem(pstrdup(workspace),
2210                                                                   (Node *) makeString(pstrdup(startvalue))));
2211         }
2212         else if (state != CS_WAITKEY)
2213                 ereport(ERROR,
2214                                 (errcode(ERRCODE_SYNTAX_ERROR),
2215                                  errmsg("invalid parameter list format: \"%s\"",
2216                                                 text_to_cstring(in))));
2217
2218         pfree(workspace);
2219
2220         return result;
2221 }