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