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