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