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 "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 											NameStr(dict->dictname),
300 											ALLOCSET_SMALL_SIZES);
301 		}
302 		else
303 		{
304 			/* Clear the existing entry's private context */
305 			saveCtx = entry->dictCtx;
306 			MemoryContextResetAndDeleteChildren(saveCtx);
307 		}
308 
309 		MemSet(entry, 0, sizeof(TSDictionaryCacheEntry));
310 		entry->dictId = dictId;
311 		entry->dictCtx = saveCtx;
312 
313 		entry->lexizeOid = template->tmpllexize;
314 
315 		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 			oldcontext = MemoryContextSwitchTo(entry->dictCtx);
327 
328 			opt = SysCacheGetAttr(TSDICTOID, tpdict,
329 								  Anum_pg_ts_dict_dictinitoption,
330 								  &isnull);
331 			if (isnull)
332 				dictoptions = NIL;
333 			else
334 				dictoptions = deserialize_deflist(opt);
335 
336 			entry->dictData =
337 				DatumGetPointer(OidFunctionCall1(template->tmplinit,
338 												 PointerGetDatum(dictoptions)));
339 
340 			MemoryContextSwitchTo(oldcontext);
341 		}
342 
343 		ReleaseSysCache(tptmpl);
344 		ReleaseSysCache(tpdict);
345 
346 		fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
347 
348 		entry->isvalid = true;
349 	}
350 
351 	lastUsedDictionary = entry;
352 
353 	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
init_ts_config_cache(void)362 init_ts_config_cache(void)
363 {
364 	HASHCTL		ctl;
365 
366 	MemSet(&ctl, 0, sizeof(ctl));
367 	ctl.keysize = sizeof(Oid);
368 	ctl.entrysize = sizeof(TSConfigCacheEntry);
369 	TSConfigCacheHash = hash_create("Tsearch configuration cache", 16,
370 									&ctl, HASH_ELEM | HASH_BLOBS);
371 	/* Flush cache on pg_ts_config and pg_ts_config_map changes */
372 	CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack,
373 								  PointerGetDatum(TSConfigCacheHash));
374 	CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack,
375 								  PointerGetDatum(TSConfigCacheHash));
376 
377 	/* Also make sure CacheMemoryContext exists */
378 	if (!CacheMemoryContext)
379 		CreateCacheMemoryContext();
380 }
381 
382 /*
383  * Fetch configuration cache entry
384  */
385 TSConfigCacheEntry *
lookup_ts_config_cache(Oid cfgId)386 lookup_ts_config_cache(Oid cfgId)
387 {
388 	TSConfigCacheEntry *entry;
389 
390 	if (TSConfigCacheHash == NULL)
391 	{
392 		/* First time through: initialize the hash table */
393 		init_ts_config_cache();
394 	}
395 
396 	/* Check single-entry cache */
397 	if (lastUsedConfig && lastUsedConfig->cfgId == cfgId &&
398 		lastUsedConfig->isvalid)
399 		return lastUsedConfig;
400 
401 	/* Try to look up an existing entry */
402 	entry = (TSConfigCacheEntry *) hash_search(TSConfigCacheHash,
403 											   (void *) &cfgId,
404 											   HASH_FIND, NULL);
405 	if (entry == NULL || !entry->isvalid)
406 	{
407 		/*
408 		 * If we didn't find one, we want to make one. But first look up the
409 		 * object to be sure the OID is real.
410 		 */
411 		HeapTuple	tp;
412 		Form_pg_ts_config cfg;
413 		Relation	maprel;
414 		Relation	mapidx;
415 		ScanKeyData mapskey;
416 		SysScanDesc mapscan;
417 		HeapTuple	maptup;
418 		ListDictionary maplists[MAXTOKENTYPE + 1];
419 		Oid			mapdicts[MAXDICTSPERTT];
420 		int			maxtokentype;
421 		int			ndicts;
422 		int			i;
423 
424 		tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
425 		if (!HeapTupleIsValid(tp))
426 			elog(ERROR, "cache lookup failed for text search configuration %u",
427 				 cfgId);
428 		cfg = (Form_pg_ts_config) GETSTRUCT(tp);
429 
430 		/*
431 		 * Sanity checks
432 		 */
433 		if (!OidIsValid(cfg->cfgparser))
434 			elog(ERROR, "text search configuration %u has no parser", cfgId);
435 
436 		if (entry == NULL)
437 		{
438 			bool		found;
439 
440 			/* Now make the cache entry */
441 			entry = (TSConfigCacheEntry *)
442 				hash_search(TSConfigCacheHash,
443 							(void *) &cfgId,
444 							HASH_ENTER, &found);
445 			Assert(!found);		/* it wasn't there a moment ago */
446 		}
447 		else
448 		{
449 			/* Cleanup old contents */
450 			if (entry->map)
451 			{
452 				for (i = 0; i < entry->lenmap; i++)
453 					if (entry->map[i].dictIds)
454 						pfree(entry->map[i].dictIds);
455 				pfree(entry->map);
456 			}
457 		}
458 
459 		MemSet(entry, 0, sizeof(TSConfigCacheEntry));
460 		entry->cfgId = cfgId;
461 		entry->prsId = cfg->cfgparser;
462 
463 		ReleaseSysCache(tp);
464 
465 		/*
466 		 * Scan pg_ts_config_map to gather dictionary list for each token type
467 		 *
468 		 * Because the index is on (mapcfg, maptokentype, mapseqno), we will
469 		 * see the entries in maptokentype order, and in mapseqno order for
470 		 * each token type, even though we didn't explicitly ask for that.
471 		 */
472 		MemSet(maplists, 0, sizeof(maplists));
473 		maxtokentype = 0;
474 		ndicts = 0;
475 
476 		ScanKeyInit(&mapskey,
477 					Anum_pg_ts_config_map_mapcfg,
478 					BTEqualStrategyNumber, F_OIDEQ,
479 					ObjectIdGetDatum(cfgId));
480 
481 		maprel = heap_open(TSConfigMapRelationId, AccessShareLock);
482 		mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
483 		mapscan = systable_beginscan_ordered(maprel, mapidx,
484 											 NULL, 1, &mapskey);
485 
486 		while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL)
487 		{
488 			Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
489 			int			toktype = cfgmap->maptokentype;
490 
491 			if (toktype <= 0 || toktype > MAXTOKENTYPE)
492 				elog(ERROR, "maptokentype value %d is out of range", toktype);
493 			if (toktype < maxtokentype)
494 				elog(ERROR, "maptokentype entries are out of order");
495 			if (toktype > maxtokentype)
496 			{
497 				/* starting a new token type, but first save the prior data */
498 				if (ndicts > 0)
499 				{
500 					maplists[maxtokentype].len = ndicts;
501 					maplists[maxtokentype].dictIds = (Oid *)
502 						MemoryContextAlloc(CacheMemoryContext,
503 										   sizeof(Oid) * ndicts);
504 					memcpy(maplists[maxtokentype].dictIds, mapdicts,
505 						   sizeof(Oid) * ndicts);
506 				}
507 				maxtokentype = toktype;
508 				mapdicts[0] = cfgmap->mapdict;
509 				ndicts = 1;
510 			}
511 			else
512 			{
513 				/* continuing data for current token type */
514 				if (ndicts >= MAXDICTSPERTT)
515 					elog(ERROR, "too many pg_ts_config_map entries for one token type");
516 				mapdicts[ndicts++] = cfgmap->mapdict;
517 			}
518 		}
519 
520 		systable_endscan_ordered(mapscan);
521 		index_close(mapidx, AccessShareLock);
522 		heap_close(maprel, AccessShareLock);
523 
524 		if (ndicts > 0)
525 		{
526 			/* save the last token type's dictionaries */
527 			maplists[maxtokentype].len = ndicts;
528 			maplists[maxtokentype].dictIds = (Oid *)
529 				MemoryContextAlloc(CacheMemoryContext,
530 								   sizeof(Oid) * ndicts);
531 			memcpy(maplists[maxtokentype].dictIds, mapdicts,
532 				   sizeof(Oid) * ndicts);
533 			/* and save the overall map */
534 			entry->lenmap = maxtokentype + 1;
535 			entry->map = (ListDictionary *)
536 				MemoryContextAlloc(CacheMemoryContext,
537 								   sizeof(ListDictionary) * entry->lenmap);
538 			memcpy(entry->map, maplists,
539 				   sizeof(ListDictionary) * entry->lenmap);
540 		}
541 
542 		entry->isvalid = true;
543 	}
544 
545 	lastUsedConfig = entry;
546 
547 	return entry;
548 }
549 
550 
551 /*---------------------------------------------------
552  * GUC variable "default_text_search_config"
553  *---------------------------------------------------
554  */
555 
556 Oid
getTSCurrentConfig(bool emitError)557 getTSCurrentConfig(bool emitError)
558 {
559 	/* if we have a cached value, return it */
560 	if (OidIsValid(TSCurrentConfigCache))
561 		return TSCurrentConfigCache;
562 
563 	/* fail if GUC hasn't been set up yet */
564 	if (TSCurrentConfig == NULL || *TSCurrentConfig == '\0')
565 	{
566 		if (emitError)
567 			elog(ERROR, "text search configuration isn't set");
568 		else
569 			return InvalidOid;
570 	}
571 
572 	if (TSConfigCacheHash == NULL)
573 	{
574 		/* First time through: initialize the tsconfig inval callback */
575 		init_ts_config_cache();
576 	}
577 
578 	/* Look up the config */
579 	TSCurrentConfigCache =
580 		get_ts_config_oid(stringToQualifiedNameList(TSCurrentConfig),
581 						  !emitError);
582 
583 	return TSCurrentConfigCache;
584 }
585 
586 /* GUC check_hook for default_text_search_config */
587 bool
check_TSCurrentConfig(char ** newval,void ** extra,GucSource source)588 check_TSCurrentConfig(char **newval, void **extra, GucSource source)
589 {
590 	/*
591 	 * If we aren't inside a transaction, or connected to a database, we
592 	 * cannot do the catalog accesses necessary to verify the config name.
593 	 * Must accept it on faith.
594 	 */
595 	if (IsTransactionState() && MyDatabaseId != InvalidOid)
596 	{
597 		Oid			cfgId;
598 		HeapTuple	tuple;
599 		Form_pg_ts_config cfg;
600 		char	   *buf;
601 
602 		cfgId = get_ts_config_oid(stringToQualifiedNameList(*newval), true);
603 
604 		/*
605 		 * When source == PGC_S_TEST, don't throw a hard error for a
606 		 * nonexistent configuration, only a NOTICE.  See comments in guc.h.
607 		 */
608 		if (!OidIsValid(cfgId))
609 		{
610 			if (source == PGC_S_TEST)
611 			{
612 				ereport(NOTICE,
613 						(errcode(ERRCODE_UNDEFINED_OBJECT),
614 						 errmsg("text search configuration \"%s\" does not exist", *newval)));
615 				return true;
616 			}
617 			else
618 				return false;
619 		}
620 
621 		/*
622 		 * Modify the actually stored value to be fully qualified, to ensure
623 		 * later changes of search_path don't affect it.
624 		 */
625 		tuple = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
626 		if (!HeapTupleIsValid(tuple))
627 			elog(ERROR, "cache lookup failed for text search configuration %u",
628 				 cfgId);
629 		cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
630 
631 		buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace),
632 										 NameStr(cfg->cfgname));
633 
634 		ReleaseSysCache(tuple);
635 
636 		/* GUC wants it malloc'd not palloc'd */
637 		free(*newval);
638 		*newval = strdup(buf);
639 		pfree(buf);
640 		if (!*newval)
641 			return false;
642 	}
643 
644 	return true;
645 }
646 
647 /* GUC assign_hook for default_text_search_config */
648 void
assign_TSCurrentConfig(const char * newval,void * extra)649 assign_TSCurrentConfig(const char *newval, void *extra)
650 {
651 	/* Just reset the cache to force a lookup on first use */
652 	TSCurrentConfigCache = InvalidOid;
653 }
654