Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * ts_cache.c
4 : * Tsearch related object caches.
5 : *
6 : * Tsearch performance is very sensitive to performance of parsers,
7 : * dictionaries and mapping, so lookups should be cached as much
8 : * as possible.
9 : *
10 : * Once a backend has created a cache entry for a particular TS object OID,
11 : * the cache entry will exist for the life of the backend; hence it is
12 : * safe to hold onto a pointer to the cache entry while doing things that
13 : * might result in recognizing a cache invalidation. Beware however that
14 : * subsidiary information might be deleted and reallocated somewhere else
15 : * if a cache inval and reval happens! This does not look like it will be
16 : * a big problem as long as parser and dictionary methods do not attempt
17 : * any database access.
18 : *
19 : *
20 : * Copyright (c) 2006-2024, PostgreSQL Global Development Group
21 : *
22 : * IDENTIFICATION
23 : * src/backend/utils/cache/ts_cache.c
24 : *
25 : *-------------------------------------------------------------------------
26 : */
27 : #include "postgres.h"
28 :
29 : #include "access/genam.h"
30 : #include "access/htup_details.h"
31 : #include "access/table.h"
32 : #include "access/xact.h"
33 : #include "catalog/namespace.h"
34 : #include "catalog/pg_ts_config.h"
35 : #include "catalog/pg_ts_config_map.h"
36 : #include "catalog/pg_ts_dict.h"
37 : #include "catalog/pg_ts_parser.h"
38 : #include "catalog/pg_ts_template.h"
39 : #include "commands/defrem.h"
40 : #include "miscadmin.h"
41 : #include "nodes/miscnodes.h"
42 : #include "tsearch/ts_cache.h"
43 : #include "utils/builtins.h"
44 : #include "utils/catcache.h"
45 : #include "utils/fmgroids.h"
46 : #include "utils/guc_hooks.h"
47 : #include "utils/inval.h"
48 : #include "utils/lsyscache.h"
49 : #include "utils/memutils.h"
50 : #include "utils/regproc.h"
51 : #include "utils/syscache.h"
52 :
53 :
54 : /*
55 : * MAXTOKENTYPE/MAXDICTSPERTT are arbitrary limits on the workspace size
56 : * used in lookup_ts_config_cache(). We could avoid hardwiring a limit
57 : * by making the workspace dynamically enlargeable, but it seems unlikely
58 : * to be worth the trouble.
59 : */
60 : #define MAXTOKENTYPE 256
61 : #define MAXDICTSPERTT 100
62 :
63 :
64 : static HTAB *TSParserCacheHash = NULL;
65 : static TSParserCacheEntry *lastUsedParser = NULL;
66 :
67 : static HTAB *TSDictionaryCacheHash = NULL;
68 : static TSDictionaryCacheEntry *lastUsedDictionary = NULL;
69 :
70 : static HTAB *TSConfigCacheHash = NULL;
71 : static TSConfigCacheEntry *lastUsedConfig = NULL;
72 :
73 : /*
74 : * GUC default_text_search_config, and a cache of the current config's OID
75 : */
76 : char *TSCurrentConfig = NULL;
77 :
78 : static Oid TSCurrentConfigCache = InvalidOid;
79 :
80 :
81 : /*
82 : * We use this syscache callback to detect when a visible change to a TS
83 : * catalog entry has been made, by either our own backend or another one.
84 : *
85 : * In principle we could just flush the specific cache entry that changed,
86 : * but given that TS configuration changes are probably infrequent, it
87 : * doesn't seem worth the trouble to determine that; we just flush all the
88 : * entries of the related hash table.
89 : *
90 : * We can use the same function for all TS caches by passing the hash
91 : * table address as the "arg".
92 : */
93 : static void
94 2102 : InvalidateTSCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
95 : {
96 2102 : HTAB *hash = (HTAB *) DatumGetPointer(arg);
97 : HASH_SEQ_STATUS status;
98 : TSAnyCacheEntry *entry;
99 :
100 2102 : hash_seq_init(&status, hash);
101 7240 : while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL)
102 5138 : entry->isvalid = false;
103 :
104 : /* Also invalidate the current-config cache if it's pg_ts_config */
105 2102 : if (hash == TSConfigCacheHash)
106 1884 : TSCurrentConfigCache = InvalidOid;
107 2102 : }
108 :
109 : /*
110 : * Fetch parser cache entry
111 : */
112 : TSParserCacheEntry *
113 12972 : lookup_ts_parser_cache(Oid prsId)
114 : {
115 : TSParserCacheEntry *entry;
116 :
117 12972 : if (TSParserCacheHash == NULL)
118 : {
119 : /* First time through: initialize the hash table */
120 : HASHCTL ctl;
121 :
122 210 : ctl.keysize = sizeof(Oid);
123 210 : ctl.entrysize = sizeof(TSParserCacheEntry);
124 210 : TSParserCacheHash = hash_create("Tsearch parser cache", 4,
125 : &ctl, HASH_ELEM | HASH_BLOBS);
126 : /* Flush cache on pg_ts_parser changes */
127 210 : CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack,
128 : PointerGetDatum(TSParserCacheHash));
129 :
130 : /* Also make sure CacheMemoryContext exists */
131 210 : if (!CacheMemoryContext)
132 0 : CreateCacheMemoryContext();
133 : }
134 :
135 : /* Check single-entry cache */
136 12972 : if (lastUsedParser && lastUsedParser->prsId == prsId &&
137 12762 : lastUsedParser->isvalid)
138 12762 : return lastUsedParser;
139 :
140 : /* Try to look up an existing entry */
141 210 : entry = (TSParserCacheEntry *) hash_search(TSParserCacheHash,
142 : &prsId,
143 : HASH_FIND, NULL);
144 210 : if (entry == NULL || !entry->isvalid)
145 : {
146 : /*
147 : * If we didn't find one, we want to make one. But first look up the
148 : * object to be sure the OID is real.
149 : */
150 : HeapTuple tp;
151 : Form_pg_ts_parser prs;
152 :
153 210 : tp = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId));
154 210 : if (!HeapTupleIsValid(tp))
155 0 : elog(ERROR, "cache lookup failed for text search parser %u",
156 : prsId);
157 210 : prs = (Form_pg_ts_parser) GETSTRUCT(tp);
158 :
159 : /*
160 : * Sanity checks
161 : */
162 210 : if (!OidIsValid(prs->prsstart))
163 0 : elog(ERROR, "text search parser %u has no prsstart method", prsId);
164 210 : if (!OidIsValid(prs->prstoken))
165 0 : elog(ERROR, "text search parser %u has no prstoken method", prsId);
166 210 : if (!OidIsValid(prs->prsend))
167 0 : elog(ERROR, "text search parser %u has no prsend method", prsId);
168 :
169 210 : if (entry == NULL)
170 : {
171 : bool found;
172 :
173 : /* Now make the cache entry */
174 : entry = (TSParserCacheEntry *)
175 210 : hash_search(TSParserCacheHash, &prsId, HASH_ENTER, &found);
176 : Assert(!found); /* it wasn't there a moment ago */
177 : }
178 :
179 6090 : MemSet(entry, 0, sizeof(TSParserCacheEntry));
180 210 : entry->prsId = prsId;
181 210 : entry->startOid = prs->prsstart;
182 210 : entry->tokenOid = prs->prstoken;
183 210 : entry->endOid = prs->prsend;
184 210 : entry->headlineOid = prs->prsheadline;
185 210 : entry->lextypeOid = prs->prslextype;
186 :
187 210 : ReleaseSysCache(tp);
188 :
189 210 : fmgr_info_cxt(entry->startOid, &entry->prsstart, CacheMemoryContext);
190 210 : fmgr_info_cxt(entry->tokenOid, &entry->prstoken, CacheMemoryContext);
191 210 : fmgr_info_cxt(entry->endOid, &entry->prsend, CacheMemoryContext);
192 210 : if (OidIsValid(entry->headlineOid))
193 210 : fmgr_info_cxt(entry->headlineOid, &entry->prsheadline,
194 : CacheMemoryContext);
195 :
196 210 : entry->isvalid = true;
197 : }
198 :
199 210 : lastUsedParser = entry;
200 :
201 210 : return entry;
202 : }
203 :
204 : /*
205 : * Fetch dictionary cache entry
206 : */
207 : TSDictionaryCacheEntry *
208 15144 : lookup_ts_dictionary_cache(Oid dictId)
209 : {
210 : TSDictionaryCacheEntry *entry;
211 :
212 15144 : if (TSDictionaryCacheHash == NULL)
213 : {
214 : /* First time through: initialize the hash table */
215 : HASHCTL ctl;
216 :
217 46 : ctl.keysize = sizeof(Oid);
218 46 : ctl.entrysize = sizeof(TSDictionaryCacheEntry);
219 46 : TSDictionaryCacheHash = hash_create("Tsearch dictionary cache", 8,
220 : &ctl, HASH_ELEM | HASH_BLOBS);
221 : /* Flush cache on pg_ts_dict and pg_ts_template changes */
222 46 : CacheRegisterSyscacheCallback(TSDICTOID, InvalidateTSCacheCallBack,
223 : PointerGetDatum(TSDictionaryCacheHash));
224 46 : CacheRegisterSyscacheCallback(TSTEMPLATEOID, InvalidateTSCacheCallBack,
225 : PointerGetDatum(TSDictionaryCacheHash));
226 :
227 : /* Also make sure CacheMemoryContext exists */
228 46 : if (!CacheMemoryContext)
229 0 : CreateCacheMemoryContext();
230 : }
231 :
232 : /* Check single-entry cache */
233 15144 : if (lastUsedDictionary && lastUsedDictionary->dictId == dictId &&
234 12614 : lastUsedDictionary->isvalid)
235 12578 : return lastUsedDictionary;
236 :
237 : /* Try to look up an existing entry */
238 2566 : entry = (TSDictionaryCacheEntry *) hash_search(TSDictionaryCacheHash,
239 : &dictId,
240 : HASH_FIND, NULL);
241 2566 : if (entry == NULL || !entry->isvalid)
242 : {
243 : /*
244 : * If we didn't find one, we want to make one. But first look up the
245 : * object to be sure the OID is real.
246 : */
247 : HeapTuple tpdict,
248 : tptmpl;
249 : Form_pg_ts_dict dict;
250 : Form_pg_ts_template template;
251 : MemoryContext saveCtx;
252 :
253 172 : tpdict = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
254 172 : if (!HeapTupleIsValid(tpdict))
255 0 : elog(ERROR, "cache lookup failed for text search dictionary %u",
256 : dictId);
257 172 : dict = (Form_pg_ts_dict) GETSTRUCT(tpdict);
258 :
259 : /*
260 : * Sanity checks
261 : */
262 172 : if (!OidIsValid(dict->dicttemplate))
263 0 : elog(ERROR, "text search dictionary %u has no template", dictId);
264 :
265 : /*
266 : * Retrieve dictionary's template
267 : */
268 172 : tptmpl = SearchSysCache1(TSTEMPLATEOID,
269 : ObjectIdGetDatum(dict->dicttemplate));
270 172 : if (!HeapTupleIsValid(tptmpl))
271 0 : elog(ERROR, "cache lookup failed for text search template %u",
272 : dict->dicttemplate);
273 172 : template = (Form_pg_ts_template) GETSTRUCT(tptmpl);
274 :
275 : /*
276 : * Sanity checks
277 : */
278 172 : if (!OidIsValid(template->tmpllexize))
279 0 : elog(ERROR, "text search template %u has no lexize method",
280 : template->tmpllexize);
281 :
282 172 : if (entry == NULL)
283 : {
284 : bool found;
285 :
286 : /* Now make the cache entry */
287 : entry = (TSDictionaryCacheEntry *)
288 106 : hash_search(TSDictionaryCacheHash,
289 : &dictId,
290 : HASH_ENTER, &found);
291 : Assert(!found); /* it wasn't there a moment ago */
292 :
293 : /* Create private memory context the first time through */
294 106 : saveCtx = AllocSetContextCreate(CacheMemoryContext,
295 : "TS dictionary",
296 : ALLOCSET_SMALL_SIZES);
297 106 : MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname));
298 : }
299 : else
300 : {
301 : /* Clear the existing entry's private context */
302 66 : saveCtx = entry->dictCtx;
303 : /* Don't let context's ident pointer dangle while we reset it */
304 66 : MemoryContextSetIdentifier(saveCtx, NULL);
305 66 : MemoryContextReset(saveCtx);
306 66 : MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname));
307 : }
308 :
309 1892 : MemSet(entry, 0, sizeof(TSDictionaryCacheEntry));
310 172 : entry->dictId = dictId;
311 172 : entry->dictCtx = saveCtx;
312 :
313 172 : entry->lexizeOid = template->tmpllexize;
314 :
315 172 : if (OidIsValid(template->tmplinit))
316 : {
317 : List *dictoptions;
318 : Datum opt;
319 : bool isnull;
320 : MemoryContext oldcontext;
321 :
322 : /*
323 : * Init method runs in dictionary's private memory context, and we
324 : * make sure the options are stored there too
325 : */
326 172 : oldcontext = MemoryContextSwitchTo(entry->dictCtx);
327 :
328 172 : opt = SysCacheGetAttr(TSDICTOID, tpdict,
329 : Anum_pg_ts_dict_dictinitoption,
330 : &isnull);
331 172 : if (isnull)
332 34 : dictoptions = NIL;
333 : else
334 138 : dictoptions = deserialize_deflist(opt);
335 :
336 172 : entry->dictData =
337 172 : DatumGetPointer(OidFunctionCall1(template->tmplinit,
338 : PointerGetDatum(dictoptions)));
339 :
340 172 : MemoryContextSwitchTo(oldcontext);
341 : }
342 :
343 172 : ReleaseSysCache(tptmpl);
344 172 : ReleaseSysCache(tpdict);
345 :
346 172 : fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
347 :
348 172 : entry->isvalid = true;
349 : }
350 :
351 2566 : lastUsedDictionary = entry;
352 :
353 2566 : return entry;
354 : }
355 :
356 : /*
357 : * Initialize config cache and prepare callbacks. This is split out of
358 : * lookup_ts_config_cache because we need to activate the callback before
359 : * caching TSCurrentConfigCache, too.
360 : */
361 : static void
362 38 : init_ts_config_cache(void)
363 : {
364 : HASHCTL ctl;
365 :
366 38 : ctl.keysize = sizeof(Oid);
367 38 : ctl.entrysize = sizeof(TSConfigCacheEntry);
368 38 : TSConfigCacheHash = hash_create("Tsearch configuration cache", 16,
369 : &ctl, HASH_ELEM | HASH_BLOBS);
370 : /* Flush cache on pg_ts_config and pg_ts_config_map changes */
371 38 : CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack,
372 : PointerGetDatum(TSConfigCacheHash));
373 38 : CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack,
374 : PointerGetDatum(TSConfigCacheHash));
375 :
376 : /* Also make sure CacheMemoryContext exists */
377 38 : if (!CacheMemoryContext)
378 0 : CreateCacheMemoryContext();
379 38 : }
380 :
381 : /*
382 : * Fetch configuration cache entry
383 : */
384 : TSConfigCacheEntry *
385 4950 : lookup_ts_config_cache(Oid cfgId)
386 : {
387 : TSConfigCacheEntry *entry;
388 :
389 4950 : if (TSConfigCacheHash == NULL)
390 : {
391 : /* First time through: initialize the hash table */
392 26 : init_ts_config_cache();
393 : }
394 :
395 : /* Check single-entry cache */
396 4950 : if (lastUsedConfig && lastUsedConfig->cfgId == cfgId &&
397 4804 : lastUsedConfig->isvalid)
398 4792 : return lastUsedConfig;
399 :
400 : /* Try to look up an existing entry */
401 158 : entry = (TSConfigCacheEntry *) hash_search(TSConfigCacheHash,
402 : &cfgId,
403 : HASH_FIND, NULL);
404 158 : if (entry == NULL || !entry->isvalid)
405 : {
406 : /*
407 : * If we didn't find one, we want to make one. But first look up the
408 : * object to be sure the OID is real.
409 : */
410 : HeapTuple tp;
411 : Form_pg_ts_config cfg;
412 : Relation maprel;
413 : Relation mapidx;
414 : ScanKeyData mapskey;
415 : SysScanDesc mapscan;
416 : HeapTuple maptup;
417 : ListDictionary maplists[MAXTOKENTYPE + 1];
418 : Oid mapdicts[MAXDICTSPERTT];
419 : int maxtokentype;
420 : int ndicts;
421 : int i;
422 :
423 92 : tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
424 92 : if (!HeapTupleIsValid(tp))
425 0 : elog(ERROR, "cache lookup failed for text search configuration %u",
426 : cfgId);
427 92 : cfg = (Form_pg_ts_config) GETSTRUCT(tp);
428 :
429 : /*
430 : * Sanity checks
431 : */
432 92 : if (!OidIsValid(cfg->cfgparser))
433 0 : elog(ERROR, "text search configuration %u has no parser", cfgId);
434 :
435 92 : if (entry == NULL)
436 : {
437 : bool found;
438 :
439 : /* Now make the cache entry */
440 : entry = (TSConfigCacheEntry *)
441 80 : hash_search(TSConfigCacheHash,
442 : &cfgId,
443 : HASH_ENTER, &found);
444 : Assert(!found); /* it wasn't there a moment ago */
445 : }
446 : else
447 : {
448 : /* Cleanup old contents */
449 12 : if (entry->map)
450 : {
451 288 : for (i = 0; i < entry->lenmap; i++)
452 276 : if (entry->map[i].dictIds)
453 228 : pfree(entry->map[i].dictIds);
454 12 : pfree(entry->map);
455 : }
456 : }
457 :
458 368 : MemSet(entry, 0, sizeof(TSConfigCacheEntry));
459 92 : entry->cfgId = cfgId;
460 92 : entry->prsId = cfg->cfgparser;
461 :
462 92 : ReleaseSysCache(tp);
463 :
464 : /*
465 : * Scan pg_ts_config_map to gather dictionary list for each token type
466 : *
467 : * Because the index is on (mapcfg, maptokentype, mapseqno), we will
468 : * see the entries in maptokentype order, and in mapseqno order for
469 : * each token type, even though we didn't explicitly ask for that.
470 : */
471 92 : MemSet(maplists, 0, sizeof(maplists));
472 92 : maxtokentype = 0;
473 92 : ndicts = 0;
474 :
475 92 : ScanKeyInit(&mapskey,
476 : Anum_pg_ts_config_map_mapcfg,
477 : BTEqualStrategyNumber, F_OIDEQ,
478 : ObjectIdGetDatum(cfgId));
479 :
480 92 : maprel = table_open(TSConfigMapRelationId, AccessShareLock);
481 92 : mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
482 92 : mapscan = systable_beginscan_ordered(maprel, mapidx,
483 : NULL, 1, &mapskey);
484 :
485 2074 : while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL)
486 : {
487 1982 : Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
488 1982 : int toktype = cfgmap->maptokentype;
489 :
490 1982 : if (toktype <= 0 || toktype > MAXTOKENTYPE)
491 0 : elog(ERROR, "maptokentype value %d is out of range", toktype);
492 1982 : if (toktype < maxtokentype)
493 0 : elog(ERROR, "maptokentype entries are out of order");
494 1982 : if (toktype > maxtokentype)
495 : {
496 : /* starting a new token type, but first save the prior data */
497 1712 : if (ndicts > 0)
498 : {
499 1620 : maplists[maxtokentype].len = ndicts;
500 1620 : maplists[maxtokentype].dictIds = (Oid *)
501 1620 : MemoryContextAlloc(CacheMemoryContext,
502 : sizeof(Oid) * ndicts);
503 1620 : memcpy(maplists[maxtokentype].dictIds, mapdicts,
504 : sizeof(Oid) * ndicts);
505 : }
506 1712 : maxtokentype = toktype;
507 1712 : mapdicts[0] = cfgmap->mapdict;
508 1712 : ndicts = 1;
509 : }
510 : else
511 : {
512 : /* continuing data for current token type */
513 270 : if (ndicts >= MAXDICTSPERTT)
514 0 : elog(ERROR, "too many pg_ts_config_map entries for one token type");
515 270 : mapdicts[ndicts++] = cfgmap->mapdict;
516 : }
517 : }
518 :
519 92 : systable_endscan_ordered(mapscan);
520 92 : index_close(mapidx, AccessShareLock);
521 92 : table_close(maprel, AccessShareLock);
522 :
523 92 : if (ndicts > 0)
524 : {
525 : /* save the last token type's dictionaries */
526 92 : maplists[maxtokentype].len = ndicts;
527 92 : maplists[maxtokentype].dictIds = (Oid *)
528 92 : MemoryContextAlloc(CacheMemoryContext,
529 : sizeof(Oid) * ndicts);
530 92 : memcpy(maplists[maxtokentype].dictIds, mapdicts,
531 : sizeof(Oid) * ndicts);
532 : /* and save the overall map */
533 92 : entry->lenmap = maxtokentype + 1;
534 92 : entry->map = (ListDictionary *)
535 92 : MemoryContextAlloc(CacheMemoryContext,
536 92 : sizeof(ListDictionary) * entry->lenmap);
537 92 : memcpy(entry->map, maplists,
538 92 : sizeof(ListDictionary) * entry->lenmap);
539 : }
540 :
541 92 : entry->isvalid = true;
542 : }
543 :
544 158 : lastUsedConfig = entry;
545 :
546 158 : return entry;
547 : }
548 :
549 :
550 : /*---------------------------------------------------
551 : * GUC variable "default_text_search_config"
552 : *---------------------------------------------------
553 : */
554 :
555 : Oid
556 390 : getTSCurrentConfig(bool emitError)
557 : {
558 : List *namelist;
559 :
560 : /* if we have a cached value, return it */
561 390 : if (OidIsValid(TSCurrentConfigCache))
562 360 : return TSCurrentConfigCache;
563 :
564 : /* fail if GUC hasn't been set up yet */
565 30 : if (TSCurrentConfig == NULL || *TSCurrentConfig == '\0')
566 : {
567 0 : if (emitError)
568 0 : elog(ERROR, "text search configuration isn't set");
569 : else
570 0 : return InvalidOid;
571 : }
572 :
573 30 : if (TSConfigCacheHash == NULL)
574 : {
575 : /* First time through: initialize the tsconfig inval callback */
576 12 : init_ts_config_cache();
577 : }
578 :
579 : /* Look up the config */
580 30 : if (emitError)
581 : {
582 30 : namelist = stringToQualifiedNameList(TSCurrentConfig, NULL);
583 30 : TSCurrentConfigCache = get_ts_config_oid(namelist, false);
584 : }
585 : else
586 : {
587 0 : ErrorSaveContext escontext = {T_ErrorSaveContext};
588 :
589 0 : namelist = stringToQualifiedNameList(TSCurrentConfig,
590 : (Node *) &escontext);
591 0 : if (namelist != NIL)
592 0 : TSCurrentConfigCache = get_ts_config_oid(namelist, true);
593 : else
594 0 : TSCurrentConfigCache = InvalidOid; /* bad name list syntax */
595 : }
596 :
597 30 : return TSCurrentConfigCache;
598 : }
599 :
600 : /* GUC check_hook for default_text_search_config */
601 : bool
602 10020 : check_default_text_search_config(char **newval, void **extra, GucSource source)
603 : {
604 : /*
605 : * If we aren't inside a transaction, or connected to a database, we
606 : * cannot do the catalog accesses necessary to verify the config name.
607 : * Must accept it on faith.
608 : */
609 10020 : if (IsTransactionState() && MyDatabaseId != InvalidOid)
610 : {
611 5474 : ErrorSaveContext escontext = {T_ErrorSaveContext};
612 : List *namelist;
613 : Oid cfgId;
614 : HeapTuple tuple;
615 : Form_pg_ts_config cfg;
616 : char *buf;
617 :
618 5474 : namelist = stringToQualifiedNameList(*newval,
619 : (Node *) &escontext);
620 5474 : if (namelist != NIL)
621 5474 : cfgId = get_ts_config_oid(namelist, true);
622 : else
623 0 : cfgId = InvalidOid; /* bad name list syntax */
624 :
625 : /*
626 : * When source == PGC_S_TEST, don't throw a hard error for a
627 : * nonexistent configuration, only a NOTICE. See comments in guc.h.
628 : */
629 5474 : if (!OidIsValid(cfgId))
630 : {
631 26 : if (source == PGC_S_TEST)
632 : {
633 14 : ereport(NOTICE,
634 : (errcode(ERRCODE_UNDEFINED_OBJECT),
635 : errmsg("text search configuration \"%s\" does not exist", *newval)));
636 26 : return true;
637 : }
638 : else
639 12 : return false;
640 : }
641 :
642 : /*
643 : * Modify the actually stored value to be fully qualified, to ensure
644 : * later changes of search_path don't affect it.
645 : */
646 5448 : tuple = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
647 5448 : if (!HeapTupleIsValid(tuple))
648 0 : elog(ERROR, "cache lookup failed for text search configuration %u",
649 : cfgId);
650 5448 : cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
651 :
652 5448 : buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace),
653 5448 : NameStr(cfg->cfgname));
654 :
655 5448 : ReleaseSysCache(tuple);
656 :
657 : /* GUC wants it guc_malloc'd not palloc'd */
658 5448 : guc_free(*newval);
659 5448 : *newval = guc_strdup(LOG, buf);
660 5448 : pfree(buf);
661 5448 : if (!*newval)
662 0 : return false;
663 : }
664 :
665 9994 : return true;
666 : }
667 :
668 : /* GUC assign_hook for default_text_search_config */
669 : void
670 9988 : assign_default_text_search_config(const char *newval, void *extra)
671 : {
672 : /* Just reset the cache to force a lookup on first use */
673 9988 : TSCurrentConfigCache = InvalidOid;
674 9988 : }
|