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