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