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