LCOV - code coverage report
Current view: top level - src/backend/utils/cache - ts_cache.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 198 225 88.0 %
Date: 2024-12-12 21:14:38 Functions: 8 8 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14