Coverage Report

Created: 2026-11-09 07:20

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