Header And Logo

PostgreSQL
| The world's most advanced open source database.

ts_cache.c

Go to the documentation of this file.
00001 /*-------------------------------------------------------------------------
00002  *
00003  * ts_cache.c
00004  *    Tsearch related object caches.
00005  *
00006  * Tsearch performance is very sensitive to performance of parsers,
00007  * dictionaries and mapping, so lookups should be cached as much
00008  * as possible.
00009  *
00010  * Once a backend has created a cache entry for a particular TS object OID,
00011  * the cache entry will exist for the life of the backend; hence it is
00012  * safe to hold onto a pointer to the cache entry while doing things that
00013  * might result in recognizing a cache invalidation.  Beware however that
00014  * subsidiary information might be deleted and reallocated somewhere else
00015  * if a cache inval and reval happens!  This does not look like it will be
00016  * a big problem as long as parser and dictionary methods do not attempt
00017  * any database access.
00018  *
00019  *
00020  * Copyright (c) 2006-2013, PostgreSQL Global Development Group
00021  *
00022  * IDENTIFICATION
00023  *    src/backend/utils/cache/ts_cache.c
00024  *
00025  *-------------------------------------------------------------------------
00026  */
00027 #include "postgres.h"
00028 
00029 #include "access/genam.h"
00030 #include "access/heapam.h"
00031 #include "access/htup_details.h"
00032 #include "access/xact.h"
00033 #include "catalog/indexing.h"
00034 #include "catalog/namespace.h"
00035 #include "catalog/pg_ts_config.h"
00036 #include "catalog/pg_ts_config_map.h"
00037 #include "catalog/pg_ts_dict.h"
00038 #include "catalog/pg_ts_parser.h"
00039 #include "catalog/pg_ts_template.h"
00040 #include "commands/defrem.h"
00041 #include "tsearch/ts_cache.h"
00042 #include "utils/builtins.h"
00043 #include "utils/catcache.h"
00044 #include "utils/fmgroids.h"
00045 #include "utils/inval.h"
00046 #include "utils/lsyscache.h"
00047 #include "utils/memutils.h"
00048 #include "utils/syscache.h"
00049 #include "utils/tqual.h"
00050 
00051 
00052 /*
00053  * MAXTOKENTYPE/MAXDICTSPERTT are arbitrary limits on the workspace size
00054  * used in lookup_ts_config_cache().  We could avoid hardwiring a limit
00055  * by making the workspace dynamically enlargeable, but it seems unlikely
00056  * to be worth the trouble.
00057  */
00058 #define MAXTOKENTYPE    256
00059 #define MAXDICTSPERTT   100
00060 
00061 
00062 static HTAB *TSParserCacheHash = NULL;
00063 static TSParserCacheEntry *lastUsedParser = NULL;
00064 
00065 static HTAB *TSDictionaryCacheHash = NULL;
00066 static TSDictionaryCacheEntry *lastUsedDictionary = NULL;
00067 
00068 static HTAB *TSConfigCacheHash = NULL;
00069 static TSConfigCacheEntry *lastUsedConfig = NULL;
00070 
00071 /*
00072  * GUC default_text_search_config, and a cache of the current config's OID
00073  */
00074 char       *TSCurrentConfig = NULL;
00075 
00076 static Oid  TSCurrentConfigCache = InvalidOid;
00077 
00078 
00079 /*
00080  * We use this syscache callback to detect when a visible change to a TS
00081  * catalog entry has been made, by either our own backend or another one.
00082  *
00083  * In principle we could just flush the specific cache entry that changed,
00084  * but given that TS configuration changes are probably infrequent, it
00085  * doesn't seem worth the trouble to determine that; we just flush all the
00086  * entries of the related hash table.
00087  *
00088  * We can use the same function for all TS caches by passing the hash
00089  * table address as the "arg".
00090  */
00091 static void
00092 InvalidateTSCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
00093 {
00094     HTAB       *hash = (HTAB *) DatumGetPointer(arg);
00095     HASH_SEQ_STATUS status;
00096     TSAnyCacheEntry *entry;
00097 
00098     hash_seq_init(&status, hash);
00099     while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL)
00100         entry->isvalid = false;
00101 
00102     /* Also invalidate the current-config cache if it's pg_ts_config */
00103     if (hash == TSConfigCacheHash)
00104         TSCurrentConfigCache = InvalidOid;
00105 }
00106 
00107 /*
00108  * Fetch parser cache entry
00109  */
00110 TSParserCacheEntry *
00111 lookup_ts_parser_cache(Oid prsId)
00112 {
00113     TSParserCacheEntry *entry;
00114 
00115     if (TSParserCacheHash == NULL)
00116     {
00117         /* First time through: initialize the hash table */
00118         HASHCTL     ctl;
00119 
00120         MemSet(&ctl, 0, sizeof(ctl));
00121         ctl.keysize = sizeof(Oid);
00122         ctl.entrysize = sizeof(TSParserCacheEntry);
00123         ctl.hash = oid_hash;
00124         TSParserCacheHash = hash_create("Tsearch parser cache", 4,
00125                                         &ctl, HASH_ELEM | HASH_FUNCTION);
00126         /* Flush cache on pg_ts_parser changes */
00127         CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack,
00128                                       PointerGetDatum(TSParserCacheHash));
00129 
00130         /* Also make sure CacheMemoryContext exists */
00131         if (!CacheMemoryContext)
00132             CreateCacheMemoryContext();
00133     }
00134 
00135     /* Check single-entry cache */
00136     if (lastUsedParser && lastUsedParser->prsId == prsId &&
00137         lastUsedParser->isvalid)
00138         return lastUsedParser;
00139 
00140     /* Try to look up an existing entry */
00141     entry = (TSParserCacheEntry *) hash_search(TSParserCacheHash,
00142                                                (void *) &prsId,
00143                                                HASH_FIND, NULL);
00144     if (entry == NULL || !entry->isvalid)
00145     {
00146         /*
00147          * If we didn't find one, we want to make one. But first look up the
00148          * object to be sure the OID is real.
00149          */
00150         HeapTuple   tp;
00151         Form_pg_ts_parser prs;
00152 
00153         tp = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId));
00154         if (!HeapTupleIsValid(tp))
00155             elog(ERROR, "cache lookup failed for text search parser %u",
00156                  prsId);
00157         prs = (Form_pg_ts_parser) GETSTRUCT(tp);
00158 
00159         /*
00160          * Sanity checks
00161          */
00162         if (!OidIsValid(prs->prsstart))
00163             elog(ERROR, "text search parser %u has no prsstart method", prsId);
00164         if (!OidIsValid(prs->prstoken))
00165             elog(ERROR, "text search parser %u has no prstoken method", prsId);
00166         if (!OidIsValid(prs->prsend))
00167             elog(ERROR, "text search parser %u has no prsend method", prsId);
00168 
00169         if (entry == NULL)
00170         {
00171             bool        found;
00172 
00173             /* Now make the cache entry */
00174             entry = (TSParserCacheEntry *)
00175                 hash_search(TSParserCacheHash,
00176                             (void *) &prsId,
00177                             HASH_ENTER, &found);
00178             Assert(!found);     /* it wasn't there a moment ago */
00179         }
00180 
00181         MemSet(entry, 0, sizeof(TSParserCacheEntry));
00182         entry->prsId = prsId;
00183         entry->startOid = prs->prsstart;
00184         entry->tokenOid = prs->prstoken;
00185         entry->endOid = prs->prsend;
00186         entry->headlineOid = prs->prsheadline;
00187         entry->lextypeOid = prs->prslextype;
00188 
00189         ReleaseSysCache(tp);
00190 
00191         fmgr_info_cxt(entry->startOid, &entry->prsstart, CacheMemoryContext);
00192         fmgr_info_cxt(entry->tokenOid, &entry->prstoken, CacheMemoryContext);
00193         fmgr_info_cxt(entry->endOid, &entry->prsend, CacheMemoryContext);
00194         if (OidIsValid(entry->headlineOid))
00195             fmgr_info_cxt(entry->headlineOid, &entry->prsheadline,
00196                           CacheMemoryContext);
00197 
00198         entry->isvalid = true;
00199     }
00200 
00201     lastUsedParser = entry;
00202 
00203     return entry;
00204 }
00205 
00206 /*
00207  * Fetch dictionary cache entry
00208  */
00209 TSDictionaryCacheEntry *
00210 lookup_ts_dictionary_cache(Oid dictId)
00211 {
00212     TSDictionaryCacheEntry *entry;
00213 
00214     if (TSDictionaryCacheHash == NULL)
00215     {
00216         /* First time through: initialize the hash table */
00217         HASHCTL     ctl;
00218 
00219         MemSet(&ctl, 0, sizeof(ctl));
00220         ctl.keysize = sizeof(Oid);
00221         ctl.entrysize = sizeof(TSDictionaryCacheEntry);
00222         ctl.hash = oid_hash;
00223         TSDictionaryCacheHash = hash_create("Tsearch dictionary cache", 8,
00224                                             &ctl, HASH_ELEM | HASH_FUNCTION);
00225         /* Flush cache on pg_ts_dict and pg_ts_template changes */
00226         CacheRegisterSyscacheCallback(TSDICTOID, InvalidateTSCacheCallBack,
00227                                       PointerGetDatum(TSDictionaryCacheHash));
00228         CacheRegisterSyscacheCallback(TSTEMPLATEOID, InvalidateTSCacheCallBack,
00229                                       PointerGetDatum(TSDictionaryCacheHash));
00230 
00231         /* Also make sure CacheMemoryContext exists */
00232         if (!CacheMemoryContext)
00233             CreateCacheMemoryContext();
00234     }
00235 
00236     /* Check single-entry cache */
00237     if (lastUsedDictionary && lastUsedDictionary->dictId == dictId &&
00238         lastUsedDictionary->isvalid)
00239         return lastUsedDictionary;
00240 
00241     /* Try to look up an existing entry */
00242     entry = (TSDictionaryCacheEntry *) hash_search(TSDictionaryCacheHash,
00243                                                    (void *) &dictId,
00244                                                    HASH_FIND, NULL);
00245     if (entry == NULL || !entry->isvalid)
00246     {
00247         /*
00248          * If we didn't find one, we want to make one. But first look up the
00249          * object to be sure the OID is real.
00250          */
00251         HeapTuple   tpdict,
00252                     tptmpl;
00253         Form_pg_ts_dict dict;
00254         Form_pg_ts_template template;
00255         MemoryContext saveCtx;
00256 
00257         tpdict = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
00258         if (!HeapTupleIsValid(tpdict))
00259             elog(ERROR, "cache lookup failed for text search dictionary %u",
00260                  dictId);
00261         dict = (Form_pg_ts_dict) GETSTRUCT(tpdict);
00262 
00263         /*
00264          * Sanity checks
00265          */
00266         if (!OidIsValid(dict->dicttemplate))
00267             elog(ERROR, "text search dictionary %u has no template", dictId);
00268 
00269         /*
00270          * Retrieve dictionary's template
00271          */
00272         tptmpl = SearchSysCache1(TSTEMPLATEOID,
00273                                  ObjectIdGetDatum(dict->dicttemplate));
00274         if (!HeapTupleIsValid(tptmpl))
00275             elog(ERROR, "cache lookup failed for text search template %u",
00276                  dict->dicttemplate);
00277         template = (Form_pg_ts_template) GETSTRUCT(tptmpl);
00278 
00279         /*
00280          * Sanity checks
00281          */
00282         if (!OidIsValid(template->tmpllexize))
00283             elog(ERROR, "text search template %u has no lexize method",
00284                  template->tmpllexize);
00285 
00286         if (entry == NULL)
00287         {
00288             bool        found;
00289 
00290             /* Now make the cache entry */
00291             entry = (TSDictionaryCacheEntry *)
00292                 hash_search(TSDictionaryCacheHash,
00293                             (void *) &dictId,
00294                             HASH_ENTER, &found);
00295             Assert(!found);     /* it wasn't there a moment ago */
00296 
00297             /* Create private memory context the first time through */
00298             saveCtx = AllocSetContextCreate(CacheMemoryContext,
00299                                             NameStr(dict->dictname),
00300                                             ALLOCSET_SMALL_MINSIZE,
00301                                             ALLOCSET_SMALL_INITSIZE,
00302                                             ALLOCSET_SMALL_MAXSIZE);
00303         }
00304         else
00305         {
00306             /* Clear the existing entry's private context */
00307             saveCtx = entry->dictCtx;
00308             MemoryContextResetAndDeleteChildren(saveCtx);
00309         }
00310 
00311         MemSet(entry, 0, sizeof(TSDictionaryCacheEntry));
00312         entry->dictId = dictId;
00313         entry->dictCtx = saveCtx;
00314 
00315         entry->lexizeOid = template->tmpllexize;
00316 
00317         if (OidIsValid(template->tmplinit))
00318         {
00319             List       *dictoptions;
00320             Datum       opt;
00321             bool        isnull;
00322             MemoryContext oldcontext;
00323 
00324             /*
00325              * Init method runs in dictionary's private memory context, and we
00326              * make sure the options are stored there too
00327              */
00328             oldcontext = MemoryContextSwitchTo(entry->dictCtx);
00329 
00330             opt = SysCacheGetAttr(TSDICTOID, tpdict,
00331                                   Anum_pg_ts_dict_dictinitoption,
00332                                   &isnull);
00333             if (isnull)
00334                 dictoptions = NIL;
00335             else
00336                 dictoptions = deserialize_deflist(opt);
00337 
00338             entry->dictData =
00339                 DatumGetPointer(OidFunctionCall1(template->tmplinit,
00340                                               PointerGetDatum(dictoptions)));
00341 
00342             MemoryContextSwitchTo(oldcontext);
00343         }
00344 
00345         ReleaseSysCache(tptmpl);
00346         ReleaseSysCache(tpdict);
00347 
00348         fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
00349 
00350         entry->isvalid = true;
00351     }
00352 
00353     lastUsedDictionary = entry;
00354 
00355     return entry;
00356 }
00357 
00358 /*
00359  * Initialize config cache and prepare callbacks.  This is split out of
00360  * lookup_ts_config_cache because we need to activate the callback before
00361  * caching TSCurrentConfigCache, too.
00362  */
00363 static void
00364 init_ts_config_cache(void)
00365 {
00366     HASHCTL     ctl;
00367 
00368     MemSet(&ctl, 0, sizeof(ctl));
00369     ctl.keysize = sizeof(Oid);
00370     ctl.entrysize = sizeof(TSConfigCacheEntry);
00371     ctl.hash = oid_hash;
00372     TSConfigCacheHash = hash_create("Tsearch configuration cache", 16,
00373                                     &ctl, HASH_ELEM | HASH_FUNCTION);
00374     /* Flush cache on pg_ts_config and pg_ts_config_map changes */
00375     CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack,
00376                                   PointerGetDatum(TSConfigCacheHash));
00377     CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack,
00378                                   PointerGetDatum(TSConfigCacheHash));
00379 
00380     /* Also make sure CacheMemoryContext exists */
00381     if (!CacheMemoryContext)
00382         CreateCacheMemoryContext();
00383 }
00384 
00385 /*
00386  * Fetch configuration cache entry
00387  */
00388 TSConfigCacheEntry *
00389 lookup_ts_config_cache(Oid cfgId)
00390 {
00391     TSConfigCacheEntry *entry;
00392 
00393     if (TSConfigCacheHash == NULL)
00394     {
00395         /* First time through: initialize the hash table */
00396         init_ts_config_cache();
00397     }
00398 
00399     /* Check single-entry cache */
00400     if (lastUsedConfig && lastUsedConfig->cfgId == cfgId &&
00401         lastUsedConfig->isvalid)
00402         return lastUsedConfig;
00403 
00404     /* Try to look up an existing entry */
00405     entry = (TSConfigCacheEntry *) hash_search(TSConfigCacheHash,
00406                                                (void *) &cfgId,
00407                                                HASH_FIND, NULL);
00408     if (entry == NULL || !entry->isvalid)
00409     {
00410         /*
00411          * If we didn't find one, we want to make one. But first look up the
00412          * object to be sure the OID is real.
00413          */
00414         HeapTuple   tp;
00415         Form_pg_ts_config cfg;
00416         Relation    maprel;
00417         Relation    mapidx;
00418         ScanKeyData mapskey;
00419         SysScanDesc mapscan;
00420         HeapTuple   maptup;
00421         ListDictionary maplists[MAXTOKENTYPE + 1];
00422         Oid         mapdicts[MAXDICTSPERTT];
00423         int         maxtokentype;
00424         int         ndicts;
00425         int         i;
00426 
00427         tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
00428         if (!HeapTupleIsValid(tp))
00429             elog(ERROR, "cache lookup failed for text search configuration %u",
00430                  cfgId);
00431         cfg = (Form_pg_ts_config) GETSTRUCT(tp);
00432 
00433         /*
00434          * Sanity checks
00435          */
00436         if (!OidIsValid(cfg->cfgparser))
00437             elog(ERROR, "text search configuration %u has no parser", cfgId);
00438 
00439         if (entry == NULL)
00440         {
00441             bool        found;
00442 
00443             /* Now make the cache entry */
00444             entry = (TSConfigCacheEntry *)
00445                 hash_search(TSConfigCacheHash,
00446                             (void *) &cfgId,
00447                             HASH_ENTER, &found);
00448             Assert(!found);     /* it wasn't there a moment ago */
00449         }
00450         else
00451         {
00452             /* Cleanup old contents */
00453             if (entry->map)
00454             {
00455                 for (i = 0; i < entry->lenmap; i++)
00456                     if (entry->map[i].dictIds)
00457                         pfree(entry->map[i].dictIds);
00458                 pfree(entry->map);
00459             }
00460         }
00461 
00462         MemSet(entry, 0, sizeof(TSConfigCacheEntry));
00463         entry->cfgId = cfgId;
00464         entry->prsId = cfg->cfgparser;
00465 
00466         ReleaseSysCache(tp);
00467 
00468         /*
00469          * Scan pg_ts_config_map to gather dictionary list for each token type
00470          *
00471          * Because the index is on (mapcfg, maptokentype, mapseqno), we will
00472          * see the entries in maptokentype order, and in mapseqno order for
00473          * each token type, even though we didn't explicitly ask for that.
00474          */
00475         MemSet(maplists, 0, sizeof(maplists));
00476         maxtokentype = 0;
00477         ndicts = 0;
00478 
00479         ScanKeyInit(&mapskey,
00480                     Anum_pg_ts_config_map_mapcfg,
00481                     BTEqualStrategyNumber, F_OIDEQ,
00482                     ObjectIdGetDatum(cfgId));
00483 
00484         maprel = heap_open(TSConfigMapRelationId, AccessShareLock);
00485         mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
00486         mapscan = systable_beginscan_ordered(maprel, mapidx,
00487                                              SnapshotNow, 1, &mapskey);
00488 
00489         while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL)
00490         {
00491             Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
00492             int         toktype = cfgmap->maptokentype;
00493 
00494             if (toktype <= 0 || toktype > MAXTOKENTYPE)
00495                 elog(ERROR, "maptokentype value %d is out of range", toktype);
00496             if (toktype < maxtokentype)
00497                 elog(ERROR, "maptokentype entries are out of order");
00498             if (toktype > maxtokentype)
00499             {
00500                 /* starting a new token type, but first save the prior data */
00501                 if (ndicts > 0)
00502                 {
00503                     maplists[maxtokentype].len = ndicts;
00504                     maplists[maxtokentype].dictIds = (Oid *)
00505                         MemoryContextAlloc(CacheMemoryContext,
00506                                            sizeof(Oid) * ndicts);
00507                     memcpy(maplists[maxtokentype].dictIds, mapdicts,
00508                            sizeof(Oid) * ndicts);
00509                 }
00510                 maxtokentype = toktype;
00511                 mapdicts[0] = cfgmap->mapdict;
00512                 ndicts = 1;
00513             }
00514             else
00515             {
00516                 /* continuing data for current token type */
00517                 if (ndicts >= MAXDICTSPERTT)
00518                     elog(ERROR, "too many pg_ts_config_map entries for one token type");
00519                 mapdicts[ndicts++] = cfgmap->mapdict;
00520             }
00521         }
00522 
00523         systable_endscan_ordered(mapscan);
00524         index_close(mapidx, AccessShareLock);
00525         heap_close(maprel, AccessShareLock);
00526 
00527         if (ndicts > 0)
00528         {
00529             /* save the last token type's dictionaries */
00530             maplists[maxtokentype].len = ndicts;
00531             maplists[maxtokentype].dictIds = (Oid *)
00532                 MemoryContextAlloc(CacheMemoryContext,
00533                                    sizeof(Oid) * ndicts);
00534             memcpy(maplists[maxtokentype].dictIds, mapdicts,
00535                    sizeof(Oid) * ndicts);
00536             /* and save the overall map */
00537             entry->lenmap = maxtokentype + 1;
00538             entry->map = (ListDictionary *)
00539                 MemoryContextAlloc(CacheMemoryContext,
00540                                    sizeof(ListDictionary) * entry->lenmap);
00541             memcpy(entry->map, maplists,
00542                    sizeof(ListDictionary) * entry->lenmap);
00543         }
00544 
00545         entry->isvalid = true;
00546     }
00547 
00548     lastUsedConfig = entry;
00549 
00550     return entry;
00551 }
00552 
00553 
00554 /*---------------------------------------------------
00555  * GUC variable "default_text_search_config"
00556  *---------------------------------------------------
00557  */
00558 
00559 Oid
00560 getTSCurrentConfig(bool emitError)
00561 {
00562     /* if we have a cached value, return it */
00563     if (OidIsValid(TSCurrentConfigCache))
00564         return TSCurrentConfigCache;
00565 
00566     /* fail if GUC hasn't been set up yet */
00567     if (TSCurrentConfig == NULL || *TSCurrentConfig == '\0')
00568     {
00569         if (emitError)
00570             elog(ERROR, "text search configuration isn't set");
00571         else
00572             return InvalidOid;
00573     }
00574 
00575     if (TSConfigCacheHash == NULL)
00576     {
00577         /* First time through: initialize the tsconfig inval callback */
00578         init_ts_config_cache();
00579     }
00580 
00581     /* Look up the config */
00582     TSCurrentConfigCache =
00583         get_ts_config_oid(stringToQualifiedNameList(TSCurrentConfig),
00584                           !emitError);
00585 
00586     return TSCurrentConfigCache;
00587 }
00588 
00589 /* GUC check_hook for default_text_search_config */
00590 bool
00591 check_TSCurrentConfig(char **newval, void **extra, GucSource source)
00592 {
00593     /*
00594      * If we aren't inside a transaction, we cannot do database access so
00595      * cannot verify the config name.  Must accept it on faith.
00596      */
00597     if (IsTransactionState())
00598     {
00599         Oid         cfgId;
00600         HeapTuple   tuple;
00601         Form_pg_ts_config cfg;
00602         char       *buf;
00603 
00604         cfgId = get_ts_config_oid(stringToQualifiedNameList(*newval), true);
00605 
00606         /*
00607          * When source == PGC_S_TEST, we are checking the argument of an ALTER
00608          * DATABASE SET or ALTER USER SET command.  It could be that the
00609          * intended use of the setting is for some other database, so we
00610          * should not error out if the text search configuration is not
00611          * present in the current database.  We issue a NOTICE instead.
00612          */
00613         if (!OidIsValid(cfgId))
00614         {
00615             if (source == PGC_S_TEST)
00616             {
00617                 ereport(NOTICE,
00618                         (errcode(ERRCODE_UNDEFINED_OBJECT),
00619                          errmsg("text search configuration \"%s\" does not exist", *newval)));
00620                 return true;
00621             }
00622             else
00623                 return false;
00624         }
00625 
00626         /*
00627          * Modify the actually stored value to be fully qualified, to ensure
00628          * later changes of search_path don't affect it.
00629          */
00630         tuple = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
00631         if (!HeapTupleIsValid(tuple))
00632             elog(ERROR, "cache lookup failed for text search configuration %u",
00633                  cfgId);
00634         cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
00635 
00636         buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace),
00637                                          NameStr(cfg->cfgname));
00638 
00639         ReleaseSysCache(tuple);
00640 
00641         /* GUC wants it malloc'd not palloc'd */
00642         free(*newval);
00643         *newval = strdup(buf);
00644         pfree(buf);
00645         if (!*newval)
00646             return false;
00647     }
00648 
00649     return true;
00650 }
00651 
00652 /* GUC assign_hook for default_text_search_config */
00653 void
00654 assign_TSCurrentConfig(const char *newval, void *extra)
00655 {
00656     /* Just reset the cache to force a lookup on first use */
00657     TSCurrentConfigCache = InvalidOid;
00658 }