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