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