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-2025, 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 2108 : InvalidateTSCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
95 : {
96 2108 : HTAB *hash = (HTAB *) DatumGetPointer(arg);
97 : HASH_SEQ_STATUS status;
98 : TSAnyCacheEntry *entry;
99 :
100 2108 : hash_seq_init(&status, hash);
101 7252 : while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL)
102 5144 : entry->isvalid = false;
103 :
104 : /* Also invalidate the current-config cache if it's pg_ts_config */
105 2108 : if (hash == TSConfigCacheHash)
106 1884 : TSCurrentConfigCache = InvalidOid;
107 2108 : }
108 :
109 : /*
110 : * Fetch parser cache entry
111 : */
112 : TSParserCacheEntry *
113 14098 : lookup_ts_parser_cache(Oid prsId)
114 : {
115 : TSParserCacheEntry *entry;
116 :
117 14098 : if (TSParserCacheHash == NULL)
118 : {
119 : /* First time through: initialize the hash table */
120 : HASHCTL ctl;
121 :
122 224 : ctl.keysize = sizeof(Oid);
123 224 : ctl.entrysize = sizeof(TSParserCacheEntry);
124 224 : TSParserCacheHash = hash_create("Tsearch parser cache", 4,
125 : &ctl, HASH_ELEM | HASH_BLOBS);
126 : /* Flush cache on pg_ts_parser changes */
127 224 : CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack,
128 : PointerGetDatum(TSParserCacheHash));
129 :
130 : /* Also make sure CacheMemoryContext exists */
131 224 : if (!CacheMemoryContext)
132 0 : CreateCacheMemoryContext();
133 : }
134 :
135 : /* Check single-entry cache */
136 14098 : if (lastUsedParser && lastUsedParser->prsId == prsId &&
137 13874 : lastUsedParser->isvalid)
138 13874 : return lastUsedParser;
139 :
140 : /* Try to look up an existing entry */
141 224 : entry = (TSParserCacheEntry *) hash_search(TSParserCacheHash,
142 : &prsId,
143 : HASH_FIND, NULL);
144 224 : 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 224 : tp = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId));
154 224 : if (!HeapTupleIsValid(tp))
155 0 : elog(ERROR, "cache lookup failed for text search parser %u",
156 : prsId);
157 224 : prs = (Form_pg_ts_parser) GETSTRUCT(tp);
158 :
159 : /*
160 : * Sanity checks
161 : */
162 224 : if (!OidIsValid(prs->prsstart))
163 0 : elog(ERROR, "text search parser %u has no prsstart method", prsId);
164 224 : if (!OidIsValid(prs->prstoken))
165 0 : elog(ERROR, "text search parser %u has no prstoken method", prsId);
166 224 : if (!OidIsValid(prs->prsend))
167 0 : elog(ERROR, "text search parser %u has no prsend method", prsId);
168 :
169 224 : if (entry == NULL)
170 : {
171 : bool found;
172 :
173 : /* Now make the cache entry */
174 : entry = (TSParserCacheEntry *)
175 224 : hash_search(TSParserCacheHash, &prsId, HASH_ENTER, &found);
176 : Assert(!found); /* it wasn't there a moment ago */
177 : }
178 :
179 6496 : MemSet(entry, 0, sizeof(TSParserCacheEntry));
180 224 : entry->prsId = prsId;
181 224 : entry->startOid = prs->prsstart;
182 224 : entry->tokenOid = prs->prstoken;
183 224 : entry->endOid = prs->prsend;
184 224 : entry->headlineOid = prs->prsheadline;
185 224 : entry->lextypeOid = prs->prslextype;
186 :
187 224 : ReleaseSysCache(tp);
188 :
189 224 : fmgr_info_cxt(entry->startOid, &entry->prsstart, CacheMemoryContext);
190 224 : fmgr_info_cxt(entry->tokenOid, &entry->prstoken, CacheMemoryContext);
191 224 : fmgr_info_cxt(entry->endOid, &entry->prsend, CacheMemoryContext);
192 224 : if (OidIsValid(entry->headlineOid))
193 224 : fmgr_info_cxt(entry->headlineOid, &entry->prsheadline,
194 : CacheMemoryContext);
195 :
196 224 : entry->isvalid = true;
197 : }
198 :
199 224 : lastUsedParser = entry;
200 :
201 224 : 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. This typically
325 : * results in a small amount of memory leakage, but it's not worth
326 : * complicating the API for tmplinit functions to avoid it.
327 : */
328 172 : oldcontext = MemoryContextSwitchTo(entry->dictCtx);
329 :
330 172 : opt = SysCacheGetAttr(TSDICTOID, tpdict,
331 : Anum_pg_ts_dict_dictinitoption,
332 : &isnull);
333 172 : if (isnull)
334 34 : dictoptions = NIL;
335 : else
336 138 : dictoptions = deserialize_deflist(opt);
337 :
338 172 : entry->dictData =
339 172 : DatumGetPointer(OidFunctionCall1(template->tmplinit,
340 : PointerGetDatum(dictoptions)));
341 :
342 172 : MemoryContextSwitchTo(oldcontext);
343 : }
344 :
345 172 : ReleaseSysCache(tptmpl);
346 172 : ReleaseSysCache(tpdict);
347 :
348 172 : fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
349 :
350 172 : entry->isvalid = true;
351 : }
352 :
353 2566 : lastUsedDictionary = entry;
354 :
355 2566 : return entry;
356 : }
357 :
358 : /*
359 : * Initialize config cache and prepare callbacks. This is split out of
360 : * lookup_ts_config_cache because we need to activate the callback before
361 : * caching TSCurrentConfigCache, too.
362 : */
363 : static void
364 38 : init_ts_config_cache(void)
365 : {
366 : HASHCTL ctl;
367 :
368 38 : ctl.keysize = sizeof(Oid);
369 38 : ctl.entrysize = sizeof(TSConfigCacheEntry);
370 38 : TSConfigCacheHash = hash_create("Tsearch configuration cache", 16,
371 : &ctl, HASH_ELEM | HASH_BLOBS);
372 : /* Flush cache on pg_ts_config and pg_ts_config_map changes */
373 38 : CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack,
374 : PointerGetDatum(TSConfigCacheHash));
375 38 : CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack,
376 : PointerGetDatum(TSConfigCacheHash));
377 :
378 : /* Also make sure CacheMemoryContext exists */
379 38 : if (!CacheMemoryContext)
380 0 : CreateCacheMemoryContext();
381 38 : }
382 :
383 : /*
384 : * Fetch configuration cache entry
385 : */
386 : TSConfigCacheEntry *
387 4950 : lookup_ts_config_cache(Oid cfgId)
388 : {
389 : TSConfigCacheEntry *entry;
390 :
391 4950 : if (TSConfigCacheHash == NULL)
392 : {
393 : /* First time through: initialize the hash table */
394 26 : init_ts_config_cache();
395 : }
396 :
397 : /* Check single-entry cache */
398 4950 : if (lastUsedConfig && lastUsedConfig->cfgId == cfgId &&
399 4804 : lastUsedConfig->isvalid)
400 4792 : return lastUsedConfig;
401 :
402 : /* Try to look up an existing entry */
403 158 : entry = (TSConfigCacheEntry *) hash_search(TSConfigCacheHash,
404 : &cfgId,
405 : HASH_FIND, NULL);
406 158 : if (entry == NULL || !entry->isvalid)
407 : {
408 : /*
409 : * If we didn't find one, we want to make one. But first look up the
410 : * object to be sure the OID is real.
411 : */
412 : HeapTuple tp;
413 : Form_pg_ts_config cfg;
414 : Relation maprel;
415 : Relation mapidx;
416 : ScanKeyData mapskey;
417 : SysScanDesc mapscan;
418 : HeapTuple maptup;
419 : ListDictionary maplists[MAXTOKENTYPE + 1];
420 : Oid mapdicts[MAXDICTSPERTT];
421 : int maxtokentype;
422 : int ndicts;
423 : int i;
424 :
425 92 : tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
426 92 : if (!HeapTupleIsValid(tp))
427 0 : elog(ERROR, "cache lookup failed for text search configuration %u",
428 : cfgId);
429 92 : cfg = (Form_pg_ts_config) GETSTRUCT(tp);
430 :
431 : /*
432 : * Sanity checks
433 : */
434 92 : if (!OidIsValid(cfg->cfgparser))
435 0 : elog(ERROR, "text search configuration %u has no parser", cfgId);
436 :
437 92 : if (entry == NULL)
438 : {
439 : bool found;
440 :
441 : /* Now make the cache entry */
442 : entry = (TSConfigCacheEntry *)
443 80 : hash_search(TSConfigCacheHash,
444 : &cfgId,
445 : HASH_ENTER, &found);
446 : Assert(!found); /* it wasn't there a moment ago */
447 : }
448 : else
449 : {
450 : /* Cleanup old contents */
451 12 : if (entry->map)
452 : {
453 288 : for (i = 0; i < entry->lenmap; i++)
454 276 : if (entry->map[i].dictIds)
455 228 : pfree(entry->map[i].dictIds);
456 12 : pfree(entry->map);
457 : }
458 : }
459 :
460 368 : MemSet(entry, 0, sizeof(TSConfigCacheEntry));
461 92 : entry->cfgId = cfgId;
462 92 : entry->prsId = cfg->cfgparser;
463 :
464 92 : ReleaseSysCache(tp);
465 :
466 : /*
467 : * Scan pg_ts_config_map to gather dictionary list for each token type
468 : *
469 : * Because the index is on (mapcfg, maptokentype, mapseqno), we will
470 : * see the entries in maptokentype order, and in mapseqno order for
471 : * each token type, even though we didn't explicitly ask for that.
472 : */
473 92 : MemSet(maplists, 0, sizeof(maplists));
474 92 : maxtokentype = 0;
475 92 : ndicts = 0;
476 :
477 92 : ScanKeyInit(&mapskey,
478 : Anum_pg_ts_config_map_mapcfg,
479 : BTEqualStrategyNumber, F_OIDEQ,
480 : ObjectIdGetDatum(cfgId));
481 :
482 92 : maprel = table_open(TSConfigMapRelationId, AccessShareLock);
483 92 : mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
484 92 : mapscan = systable_beginscan_ordered(maprel, mapidx,
485 : NULL, 1, &mapskey);
486 :
487 2074 : while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL)
488 : {
489 1982 : Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
490 1982 : int toktype = cfgmap->maptokentype;
491 :
492 1982 : if (toktype <= 0 || toktype > MAXTOKENTYPE)
493 0 : elog(ERROR, "maptokentype value %d is out of range", toktype);
494 1982 : if (toktype < maxtokentype)
495 0 : elog(ERROR, "maptokentype entries are out of order");
496 1982 : if (toktype > maxtokentype)
497 : {
498 : /* starting a new token type, but first save the prior data */
499 1712 : if (ndicts > 0)
500 : {
501 1620 : maplists[maxtokentype].len = ndicts;
502 1620 : maplists[maxtokentype].dictIds = (Oid *)
503 1620 : MemoryContextAlloc(CacheMemoryContext,
504 : sizeof(Oid) * ndicts);
505 1620 : memcpy(maplists[maxtokentype].dictIds, mapdicts,
506 : sizeof(Oid) * ndicts);
507 : }
508 1712 : maxtokentype = toktype;
509 1712 : mapdicts[0] = cfgmap->mapdict;
510 1712 : ndicts = 1;
511 : }
512 : else
513 : {
514 : /* continuing data for current token type */
515 270 : if (ndicts >= MAXDICTSPERTT)
516 0 : elog(ERROR, "too many pg_ts_config_map entries for one token type");
517 270 : mapdicts[ndicts++] = cfgmap->mapdict;
518 : }
519 : }
520 :
521 92 : systable_endscan_ordered(mapscan);
522 92 : index_close(mapidx, AccessShareLock);
523 92 : table_close(maprel, AccessShareLock);
524 :
525 92 : if (ndicts > 0)
526 : {
527 : /* save the last token type's dictionaries */
528 92 : maplists[maxtokentype].len = ndicts;
529 92 : maplists[maxtokentype].dictIds = (Oid *)
530 92 : MemoryContextAlloc(CacheMemoryContext,
531 : sizeof(Oid) * ndicts);
532 92 : memcpy(maplists[maxtokentype].dictIds, mapdicts,
533 : sizeof(Oid) * ndicts);
534 : /* and save the overall map */
535 92 : entry->lenmap = maxtokentype + 1;
536 92 : entry->map = (ListDictionary *)
537 92 : MemoryContextAlloc(CacheMemoryContext,
538 92 : sizeof(ListDictionary) * entry->lenmap);
539 92 : memcpy(entry->map, maplists,
540 92 : sizeof(ListDictionary) * entry->lenmap);
541 : }
542 :
543 92 : entry->isvalid = true;
544 : }
545 :
546 158 : lastUsedConfig = entry;
547 :
548 158 : return entry;
549 : }
550 :
551 :
552 : /*---------------------------------------------------
553 : * GUC variable "default_text_search_config"
554 : *---------------------------------------------------
555 : */
556 :
557 : Oid
558 390 : getTSCurrentConfig(bool emitError)
559 : {
560 : List *namelist;
561 :
562 : /* if we have a cached value, return it */
563 390 : if (OidIsValid(TSCurrentConfigCache))
564 360 : return TSCurrentConfigCache;
565 :
566 : /* fail if GUC hasn't been set up yet */
567 30 : if (TSCurrentConfig == NULL || *TSCurrentConfig == '\0')
568 : {
569 0 : if (emitError)
570 0 : elog(ERROR, "text search configuration isn't set");
571 : else
572 0 : return InvalidOid;
573 : }
574 :
575 30 : if (TSConfigCacheHash == NULL)
576 : {
577 : /* First time through: initialize the tsconfig inval callback */
578 12 : init_ts_config_cache();
579 : }
580 :
581 : /* Look up the config */
582 30 : if (emitError)
583 : {
584 30 : namelist = stringToQualifiedNameList(TSCurrentConfig, NULL);
585 30 : TSCurrentConfigCache = get_ts_config_oid(namelist, false);
586 : }
587 : else
588 : {
589 0 : ErrorSaveContext escontext = {T_ErrorSaveContext};
590 :
591 0 : namelist = stringToQualifiedNameList(TSCurrentConfig,
592 : (Node *) &escontext);
593 0 : if (namelist != NIL)
594 0 : TSCurrentConfigCache = get_ts_config_oid(namelist, true);
595 : else
596 0 : TSCurrentConfigCache = InvalidOid; /* bad name list syntax */
597 : }
598 :
599 30 : return TSCurrentConfigCache;
600 : }
601 :
602 : /* GUC check_hook for default_text_search_config */
603 : bool
604 11150 : check_default_text_search_config(char **newval, void **extra, GucSource source)
605 : {
606 : /*
607 : * If we aren't inside a transaction, or connected to a database, we
608 : * cannot do the catalog accesses necessary to verify the config name.
609 : * Must accept it on faith.
610 : */
611 11150 : if (IsTransactionState() && MyDatabaseId != InvalidOid)
612 : {
613 5530 : ErrorSaveContext escontext = {T_ErrorSaveContext};
614 : List *namelist;
615 : Oid cfgId;
616 : HeapTuple tuple;
617 : Form_pg_ts_config cfg;
618 : char *buf;
619 :
620 5530 : namelist = stringToQualifiedNameList(*newval,
621 : (Node *) &escontext);
622 5530 : if (namelist != NIL)
623 5530 : cfgId = get_ts_config_oid(namelist, true);
624 : else
625 0 : cfgId = InvalidOid; /* bad name list syntax */
626 :
627 : /*
628 : * When source == PGC_S_TEST, don't throw a hard error for a
629 : * nonexistent configuration, only a NOTICE. See comments in guc.h.
630 : */
631 5530 : if (!OidIsValid(cfgId))
632 : {
633 26 : if (source == PGC_S_TEST)
634 : {
635 14 : ereport(NOTICE,
636 : (errcode(ERRCODE_UNDEFINED_OBJECT),
637 : errmsg("text search configuration \"%s\" does not exist", *newval)));
638 26 : return true;
639 : }
640 : else
641 12 : return false;
642 : }
643 :
644 : /*
645 : * Modify the actually stored value to be fully qualified, to ensure
646 : * later changes of search_path don't affect it.
647 : */
648 5504 : tuple = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
649 5504 : if (!HeapTupleIsValid(tuple))
650 0 : elog(ERROR, "cache lookup failed for text search configuration %u",
651 : cfgId);
652 5504 : cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
653 :
654 5504 : buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace),
655 5504 : NameStr(cfg->cfgname));
656 :
657 5504 : ReleaseSysCache(tuple);
658 :
659 : /* GUC wants it guc_malloc'd not palloc'd */
660 5504 : guc_free(*newval);
661 5504 : *newval = guc_strdup(LOG, buf);
662 5504 : pfree(buf);
663 5504 : if (!*newval)
664 0 : return false;
665 : }
666 :
667 11124 : return true;
668 : }
669 :
670 : /* GUC assign_hook for default_text_search_config */
671 : void
672 11118 : assign_default_text_search_config(const char *newval, void *extra)
673 : {
674 : /* Just reset the cache to force a lookup on first use */
675 11118 : TSCurrentConfigCache = InvalidOid;
676 11118 : }
|