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