Branch data 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-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 : : #define MAXTOKENTYPE 256
62 : : #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 : 1388 : InvalidateTSCacheCallBack(Datum arg, SysCacheIdentifier cacheid, uint32 hashvalue)
96 : : {
97 : 1388 : HTAB *hash = (HTAB *) DatumGetPointer(arg);
98 : : HASH_SEQ_STATUS status;
99 : : TSAnyCacheEntry *entry;
100 : :
101 : 1388 : hash_seq_init(&status, hash);
102 [ + + ]: 4800 : while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL)
103 : 3412 : entry->isvalid = false;
104 : :
105 : : /* Also invalidate the current-config cache if it's pg_ts_config */
106 [ + + ]: 1388 : if (hash == TSConfigCacheHash)
107 : 1256 : TSCurrentConfigCache = InvalidOid;
108 : 1388 : }
109 : :
110 : : /*
111 : : * Fetch parser cache entry
112 : : */
113 : : TSParserCacheEntry *
114 : 9578 : lookup_ts_parser_cache(Oid prsId)
115 : : {
116 : : TSParserCacheEntry *entry;
117 : :
118 [ + + ]: 9578 : if (TSParserCacheHash == NULL)
119 : : {
120 : : /* First time through: initialize the hash table */
121 : : HASHCTL ctl;
122 : :
123 : 127 : ctl.keysize = sizeof(Oid);
124 : 127 : ctl.entrysize = sizeof(TSParserCacheEntry);
125 : 127 : TSParserCacheHash = hash_create("Tsearch parser cache", 4,
126 : : &ctl, HASH_ELEM | HASH_BLOBS);
127 : : /* Flush cache on pg_ts_parser changes */
128 : 127 : CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack,
129 : : PointerGetDatum(TSParserCacheHash));
130 : :
131 : : /* Also make sure CacheMemoryContext exists */
132 [ - + ]: 127 : if (!CacheMemoryContext)
133 : 0 : CreateCacheMemoryContext();
134 : : }
135 : :
136 : : /* Check single-entry cache */
137 [ + + + - ]: 9578 : if (lastUsedParser && lastUsedParser->prsId == prsId &&
138 [ + - ]: 9451 : lastUsedParser->isvalid)
139 : 9451 : return lastUsedParser;
140 : :
141 : : /* Try to look up an existing entry */
142 : 127 : entry = (TSParserCacheEntry *) hash_search(TSParserCacheHash,
143 : : &prsId,
144 : : HASH_FIND, NULL);
145 [ - + - - ]: 127 : 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 : 127 : tp = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId));
155 [ - + ]: 127 : if (!HeapTupleIsValid(tp))
156 [ # # ]: 0 : elog(ERROR, "cache lookup failed for text search parser %u",
157 : : prsId);
158 : 127 : prs = (Form_pg_ts_parser) GETSTRUCT(tp);
159 : :
160 : : /*
161 : : * Sanity checks
162 : : */
163 [ - + ]: 127 : if (!OidIsValid(prs->prsstart))
164 [ # # ]: 0 : elog(ERROR, "text search parser %u has no prsstart method", prsId);
165 [ - + ]: 127 : if (!OidIsValid(prs->prstoken))
166 [ # # ]: 0 : elog(ERROR, "text search parser %u has no prstoken method", prsId);
167 [ - + ]: 127 : if (!OidIsValid(prs->prsend))
168 [ # # ]: 0 : elog(ERROR, "text search parser %u has no prsend method", prsId);
169 : :
170 [ + - ]: 127 : if (entry == NULL)
171 : : {
172 : : bool found;
173 : :
174 : : /* Now make the cache entry */
175 : : entry = (TSParserCacheEntry *)
176 : 127 : hash_search(TSParserCacheHash, &prsId, HASH_ENTER, &found);
177 : : Assert(!found); /* it wasn't there a moment ago */
178 : : }
179 : :
180 [ + - + - : 3683 : MemSet(entry, 0, sizeof(TSParserCacheEntry));
+ - + - +
+ ]
181 : 127 : entry->prsId = prsId;
182 : 127 : entry->startOid = prs->prsstart;
183 : 127 : entry->tokenOid = prs->prstoken;
184 : 127 : entry->endOid = prs->prsend;
185 : 127 : entry->headlineOid = prs->prsheadline;
186 : 127 : entry->lextypeOid = prs->prslextype;
187 : :
188 : 127 : ReleaseSysCache(tp);
189 : :
190 : 127 : fmgr_info_cxt(entry->startOid, &entry->prsstart, CacheMemoryContext);
191 : 127 : fmgr_info_cxt(entry->tokenOid, &entry->prstoken, CacheMemoryContext);
192 : 127 : fmgr_info_cxt(entry->endOid, &entry->prsend, CacheMemoryContext);
193 [ + - ]: 127 : if (OidIsValid(entry->headlineOid))
194 : 127 : fmgr_info_cxt(entry->headlineOid, &entry->prsheadline,
195 : : CacheMemoryContext);
196 : :
197 : 127 : entry->isvalid = true;
198 : : }
199 : :
200 : 127 : lastUsedParser = entry;
201 : :
202 : 127 : return entry;
203 : : }
204 : :
205 : : /*
206 : : * Fetch dictionary cache entry
207 : : */
208 : : TSDictionaryCacheEntry *
209 : 12399 : lookup_ts_dictionary_cache(Oid dictId)
210 : : {
211 : : TSDictionaryCacheEntry *entry;
212 : :
213 [ + + ]: 12399 : if (TSDictionaryCacheHash == NULL)
214 : : {
215 : : /* First time through: initialize the hash table */
216 : : HASHCTL ctl;
217 : :
218 : 32 : ctl.keysize = sizeof(Oid);
219 : 32 : ctl.entrysize = sizeof(TSDictionaryCacheEntry);
220 : 32 : TSDictionaryCacheHash = hash_create("Tsearch dictionary cache", 8,
221 : : &ctl, HASH_ELEM | HASH_BLOBS);
222 : : /* Flush cache on pg_ts_dict and pg_ts_template changes */
223 : 32 : CacheRegisterSyscacheCallback(TSDICTOID, InvalidateTSCacheCallBack,
224 : : PointerGetDatum(TSDictionaryCacheHash));
225 : 32 : CacheRegisterSyscacheCallback(TSTEMPLATEOID, InvalidateTSCacheCallBack,
226 : : PointerGetDatum(TSDictionaryCacheHash));
227 : :
228 : : /* Also make sure CacheMemoryContext exists */
229 [ - + ]: 32 : if (!CacheMemoryContext)
230 : 0 : CreateCacheMemoryContext();
231 : : }
232 : :
233 : : /* Check single-entry cache */
234 [ + + + + ]: 12399 : if (lastUsedDictionary && lastUsedDictionary->dictId == dictId &&
235 [ + + ]: 10354 : lastUsedDictionary->isvalid)
236 : 10333 : return lastUsedDictionary;
237 : :
238 : : /* Try to look up an existing entry */
239 : 2066 : entry = (TSDictionaryCacheEntry *) hash_search(TSDictionaryCacheHash,
240 : : &dictId,
241 : : HASH_FIND, NULL);
242 [ + + + + ]: 2066 : if (entry == NULL || !entry->isvalid)
243 : : {
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 : : HeapTuple tpdict,
249 : : tptmpl;
250 : : Form_pg_ts_dict dict;
251 : : Form_pg_ts_template template;
252 : : MemoryContext saveCtx;
253 : :
254 : 113 : tpdict = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
255 [ - + ]: 113 : if (!HeapTupleIsValid(tpdict))
256 [ # # ]: 0 : elog(ERROR, "cache lookup failed for text search dictionary %u",
257 : : dictId);
258 : 113 : dict = (Form_pg_ts_dict) GETSTRUCT(tpdict);
259 : :
260 : : /*
261 : : * Sanity checks
262 : : */
263 [ - + ]: 113 : 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 : 113 : tptmpl = SearchSysCache1(TSTEMPLATEOID,
270 : : ObjectIdGetDatum(dict->dicttemplate));
271 [ - + ]: 113 : if (!HeapTupleIsValid(tptmpl))
272 [ # # ]: 0 : elog(ERROR, "cache lookup failed for text search template %u",
273 : : dict->dicttemplate);
274 : 113 : template = (Form_pg_ts_template) GETSTRUCT(tptmpl);
275 : :
276 : : /*
277 : : * Sanity checks
278 : : */
279 [ - + ]: 113 : if (!OidIsValid(template->tmpllexize))
280 [ # # ]: 0 : elog(ERROR, "text search template %u has no lexize method",
281 : : template->tmpllexize);
282 : :
283 : : /*
284 : : * OK, create or clear out the hashtable entry
285 : : */
286 [ + + ]: 113 : if (entry == NULL)
287 : : {
288 : : bool found;
289 : :
290 : : entry = (TSDictionaryCacheEntry *)
291 : 72 : hash_search(TSDictionaryCacheHash,
292 : : &dictId,
293 : : HASH_ENTER, &found);
294 : : Assert(!found); /* it wasn't there a moment ago */
295 : :
296 : 72 : memset(entry, 0, sizeof(TSDictionaryCacheEntry));
297 : 72 : entry->dictId = dictId;
298 : 72 : saveCtx = NULL;
299 : : }
300 : : else
301 : : {
302 : 41 : saveCtx = entry->dictCtx; /* could be NULL if we failed before */
303 : 41 : memset(entry, 0, sizeof(TSDictionaryCacheEntry));
304 : 41 : entry->dictId = dictId;
305 : 41 : entry->dictCtx = saveCtx;
306 : : }
307 : :
308 : : /*
309 : : * Create or clear the entry's private memory context
310 : : */
311 [ + + ]: 113 : if (saveCtx == NULL)
312 : : {
313 : 72 : saveCtx = AllocSetContextCreate(CacheMemoryContext,
314 : : "TS dictionary",
315 : : ALLOCSET_SMALL_SIZES);
316 : 72 : entry->dictCtx = saveCtx;
317 : 72 : MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname));
318 : : }
319 : : else
320 : : {
321 : : /* Don't let context's ident pointer dangle while we reset it */
322 : 41 : MemoryContextSetIdentifier(saveCtx, NULL);
323 : 41 : MemoryContextReset(saveCtx);
324 : 41 : MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname));
325 : : }
326 : :
327 : 113 : entry->lexizeOid = template->tmpllexize;
328 : :
329 [ + - ]: 113 : if (OidIsValid(template->tmplinit))
330 : : {
331 : : List *dictoptions;
332 : : Datum opt;
333 : : bool isnull;
334 : : 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 : 113 : oldcontext = MemoryContextSwitchTo(entry->dictCtx);
343 : :
344 : 113 : opt = SysCacheGetAttr(TSDICTOID, tpdict,
345 : : Anum_pg_ts_dict_dictinitoption,
346 : : &isnull);
347 [ + + ]: 113 : if (isnull)
348 : 21 : dictoptions = NIL;
349 : : else
350 : 92 : dictoptions = deserialize_deflist(opt);
351 : :
352 : 113 : entry->dictData =
353 : 113 : DatumGetPointer(OidFunctionCall1(template->tmplinit,
354 : : PointerGetDatum(dictoptions)));
355 : :
356 : 113 : MemoryContextSwitchTo(oldcontext);
357 : : }
358 : :
359 : 113 : ReleaseSysCache(tptmpl);
360 : 113 : ReleaseSysCache(tpdict);
361 : :
362 : 113 : fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
363 : :
364 : 113 : entry->isvalid = true;
365 : : }
366 : :
367 : 2066 : lastUsedDictionary = entry;
368 : :
369 : 2066 : return entry;
370 : : }
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 : 28 : init_ts_config_cache(void)
379 : : {
380 : : HASHCTL ctl;
381 : :
382 : 28 : ctl.keysize = sizeof(Oid);
383 : 28 : ctl.entrysize = sizeof(TSConfigCacheEntry);
384 : 28 : TSConfigCacheHash = hash_create("Tsearch configuration cache", 16,
385 : : &ctl, HASH_ELEM | HASH_BLOBS);
386 : : /* Flush cache on pg_ts_config and pg_ts_config_map changes */
387 : 28 : CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack,
388 : : PointerGetDatum(TSConfigCacheHash));
389 : 28 : CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack,
390 : : PointerGetDatum(TSConfigCacheHash));
391 : :
392 : : /* Also make sure CacheMemoryContext exists */
393 [ - + ]: 28 : if (!CacheMemoryContext)
394 : 0 : CreateCacheMemoryContext();
395 : 28 : }
396 : :
397 : : /*
398 : : * Fetch configuration cache entry
399 : : */
400 : : TSConfigCacheEntry *
401 : 4077 : lookup_ts_config_cache(Oid cfgId)
402 : : {
403 : : TSConfigCacheEntry *entry;
404 : :
405 [ + + ]: 4077 : if (TSConfigCacheHash == NULL)
406 : : {
407 : : /* First time through: initialize the hash table */
408 : 16 : init_ts_config_cache();
409 : : }
410 : :
411 : : /* Check single-entry cache */
412 [ + + + + ]: 4077 : if (lastUsedConfig && lastUsedConfig->cfgId == cfgId &&
413 [ + + ]: 3977 : lastUsedConfig->isvalid)
414 : 3969 : return lastUsedConfig;
415 : :
416 : : /* Try to look up an existing entry */
417 : 108 : entry = (TSConfigCacheEntry *) hash_search(TSConfigCacheHash,
418 : : &cfgId,
419 : : HASH_FIND, NULL);
420 [ + + + + ]: 108 : if (entry == NULL || !entry->isvalid)
421 : : {
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 : : HeapTuple tp;
427 : : Form_pg_ts_config cfg;
428 : : Relation maprel;
429 : : Relation mapidx;
430 : : ScanKeyData mapskey;
431 : : SysScanDesc mapscan;
432 : : HeapTuple maptup;
433 : : ListDictionary maplists[MAXTOKENTYPE + 1];
434 : : Oid mapdicts[MAXDICTSPERTT];
435 : : int maxtokentype;
436 : : int ndicts;
437 : : int i;
438 : :
439 : 64 : tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
440 [ - + ]: 64 : if (!HeapTupleIsValid(tp))
441 [ # # ]: 0 : elog(ERROR, "cache lookup failed for text search configuration %u",
442 : : cfgId);
443 : 64 : cfg = (Form_pg_ts_config) GETSTRUCT(tp);
444 : :
445 : : /*
446 : : * Sanity checks
447 : : */
448 [ - + ]: 64 : if (!OidIsValid(cfg->cfgparser))
449 [ # # ]: 0 : elog(ERROR, "text search configuration %u has no parser", cfgId);
450 : :
451 [ + + ]: 64 : if (entry == NULL)
452 : : {
453 : : bool found;
454 : :
455 : : /* Now make the cache entry */
456 : : entry = (TSConfigCacheEntry *)
457 : 56 : hash_search(TSConfigCacheHash,
458 : : &cfgId,
459 : : HASH_ENTER, &found);
460 : : Assert(!found); /* it wasn't there a moment ago */
461 : : }
462 : : else
463 : : {
464 : : /* Cleanup old contents */
465 [ + - ]: 8 : if (entry->map)
466 : : {
467 [ + + ]: 192 : for (i = 0; i < entry->lenmap; i++)
468 [ + + ]: 184 : if (entry->map[i].dictIds)
469 : 152 : pfree(entry->map[i].dictIds);
470 : 8 : pfree(entry->map);
471 : : }
472 : : }
473 : :
474 [ + - + - : 256 : MemSet(entry, 0, sizeof(TSConfigCacheEntry));
+ - + - +
+ ]
475 : 64 : entry->cfgId = cfgId;
476 : 64 : entry->prsId = cfg->cfgparser;
477 : :
478 : 64 : 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 [ + - + - : 64 : MemSet(maplists, 0, sizeof(maplists));
+ - - + -
- ]
488 : 64 : maxtokentype = 0;
489 : 64 : ndicts = 0;
490 : :
491 : 64 : ScanKeyInit(&mapskey,
492 : : Anum_pg_ts_config_map_mapcfg,
493 : : BTEqualStrategyNumber, F_OIDEQ,
494 : : ObjectIdGetDatum(cfgId));
495 : :
496 : 64 : maprel = table_open(TSConfigMapRelationId, AccessShareLock);
497 : 64 : mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
498 : 64 : mapscan = systable_beginscan_ordered(maprel, mapidx,
499 : : NULL, 1, &mapskey);
500 : :
501 [ + + ]: 1442 : while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL)
502 : : {
503 : 1378 : Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
504 : 1378 : int toktype = cfgmap->maptokentype;
505 : :
506 [ + - - + ]: 1378 : if (toktype <= 0 || toktype > MAXTOKENTYPE)
507 [ # # ]: 0 : elog(ERROR, "maptokentype value %d is out of range", toktype);
508 [ - + ]: 1378 : if (toktype < maxtokentype)
509 [ # # ]: 0 : elog(ERROR, "maptokentype entries are out of order");
510 [ + + ]: 1378 : if (toktype > maxtokentype)
511 : : {
512 : : /* starting a new token type, but first save the prior data */
513 [ + + ]: 1198 : if (ndicts > 0)
514 : : {
515 : 1134 : maplists[maxtokentype].len = ndicts;
516 : 1134 : maplists[maxtokentype].dictIds = (Oid *)
517 : 1134 : MemoryContextAlloc(CacheMemoryContext,
518 : : sizeof(Oid) * ndicts);
519 : 1134 : memcpy(maplists[maxtokentype].dictIds, mapdicts,
520 : : sizeof(Oid) * ndicts);
521 : : }
522 : 1198 : maxtokentype = toktype;
523 : 1198 : mapdicts[0] = cfgmap->mapdict;
524 : 1198 : ndicts = 1;
525 : : }
526 : : else
527 : : {
528 : : /* continuing data for current token type */
529 [ - + ]: 180 : if (ndicts >= MAXDICTSPERTT)
530 [ # # ]: 0 : elog(ERROR, "too many pg_ts_config_map entries for one token type");
531 : 180 : mapdicts[ndicts++] = cfgmap->mapdict;
532 : : }
533 : : }
534 : :
535 : 64 : systable_endscan_ordered(mapscan);
536 : 64 : index_close(mapidx, AccessShareLock);
537 : 64 : table_close(maprel, AccessShareLock);
538 : :
539 [ + - ]: 64 : if (ndicts > 0)
540 : : {
541 : : /* save the last token type's dictionaries */
542 : 64 : maplists[maxtokentype].len = ndicts;
543 : 64 : maplists[maxtokentype].dictIds = (Oid *)
544 : 64 : MemoryContextAlloc(CacheMemoryContext,
545 : : sizeof(Oid) * ndicts);
546 : 64 : memcpy(maplists[maxtokentype].dictIds, mapdicts,
547 : : sizeof(Oid) * ndicts);
548 : : /* and save the overall map */
549 : 64 : entry->lenmap = maxtokentype + 1;
550 : 64 : entry->map = (ListDictionary *)
551 : 64 : MemoryContextAlloc(CacheMemoryContext,
552 : 64 : sizeof(ListDictionary) * entry->lenmap);
553 : 64 : memcpy(entry->map, maplists,
554 : 64 : sizeof(ListDictionary) * entry->lenmap);
555 : : }
556 : :
557 : 64 : entry->isvalid = true;
558 : : }
559 : :
560 : 108 : lastUsedConfig = entry;
561 : :
562 : 108 : return entry;
563 : : }
564 : :
565 : :
566 : : /*---------------------------------------------------
567 : : * GUC variable "default_text_search_config"
568 : : *---------------------------------------------------
569 : : */
570 : :
571 : : Oid
572 : 300 : getTSCurrentConfig(bool emitError)
573 : : {
574 : : List *namelist;
575 : :
576 : : /* if we have a cached value, return it */
577 [ + + ]: 300 : if (OidIsValid(TSCurrentConfigCache))
578 : 276 : return TSCurrentConfigCache;
579 : :
580 : : /* fail if GUC hasn't been set up yet */
581 [ + - - + ]: 24 : if (TSCurrentConfig == NULL || *TSCurrentConfig == '\0')
582 : : {
583 [ # # ]: 0 : if (emitError)
584 [ # # ]: 0 : elog(ERROR, "text search configuration isn't set");
585 : : else
586 : 0 : return InvalidOid;
587 : : }
588 : :
589 [ + + ]: 24 : if (TSConfigCacheHash == NULL)
590 : : {
591 : : /* First time through: initialize the tsconfig inval callback */
592 : 12 : init_ts_config_cache();
593 : : }
594 : :
595 : : /* Look up the config */
596 [ + - ]: 24 : if (emitError)
597 : : {
598 : 24 : namelist = stringToQualifiedNameList(TSCurrentConfig, NULL);
599 : 24 : TSCurrentConfigCache = get_ts_config_oid(namelist, false);
600 : : }
601 : : else
602 : : {
603 : 0 : ErrorSaveContext escontext = {T_ErrorSaveContext};
604 : :
605 : 0 : namelist = stringToQualifiedNameList(TSCurrentConfig,
606 : : (Node *) &escontext);
607 [ # # ]: 0 : if (namelist != NIL)
608 : 0 : TSCurrentConfigCache = get_ts_config_oid(namelist, true);
609 : : else
610 : 0 : TSCurrentConfigCache = InvalidOid; /* bad name list syntax */
611 : : }
612 : :
613 : 24 : return TSCurrentConfigCache;
614 : : }
615 : :
616 : : /* GUC check_hook for default_text_search_config */
617 : : bool
618 : 7432 : check_default_text_search_config(char **newval, void **extra, GucSource source)
619 : : {
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 [ + + + - ]: 7432 : if (IsTransactionState() && MyDatabaseId != InvalidOid)
626 : : {
627 : 4059 : ErrorSaveContext escontext = {T_ErrorSaveContext};
628 : : List *namelist;
629 : : Oid cfgId;
630 : : HeapTuple tuple;
631 : : Form_pg_ts_config cfg;
632 : : char *buf;
633 : :
634 : 4059 : namelist = stringToQualifiedNameList(*newval,
635 : : (Node *) &escontext);
636 [ + - ]: 4059 : if (namelist != NIL)
637 : 4059 : cfgId = get_ts_config_oid(namelist, true);
638 : : 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 [ + + ]: 4059 : if (!OidIsValid(cfgId))
646 : : {
647 [ + + ]: 17 : if (source == PGC_S_TEST)
648 : : {
649 [ + + ]: 9 : ereport(NOTICE,
650 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
651 : : errmsg("text search configuration \"%s\" does not exist", *newval)));
652 : 17 : return true;
653 : : }
654 : : else
655 : 8 : return false;
656 : : }
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 : 4042 : tuple = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
663 [ - + ]: 4042 : if (!HeapTupleIsValid(tuple))
664 [ # # ]: 0 : elog(ERROR, "cache lookup failed for text search configuration %u",
665 : : cfgId);
666 : 4042 : cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
667 : :
668 : 4042 : buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace),
669 : 4042 : NameStr(cfg->cfgname));
670 : :
671 : 4042 : ReleaseSysCache(tuple);
672 : :
673 : : /* GUC wants it guc_malloc'd not palloc'd */
674 : 4042 : guc_free(*newval);
675 : 4042 : *newval = guc_strdup(LOG, buf);
676 : 4042 : pfree(buf);
677 [ - + ]: 4042 : if (!*newval)
678 : 0 : return false;
679 : : }
680 : :
681 : 7415 : return true;
682 : : }
683 : :
684 : : /* GUC assign_hook for default_text_search_config */
685 : : void
686 : 7411 : assign_default_text_search_config(const char *newval, void *extra)
687 : : {
688 : : /* Just reset the cache to force a lookup on first use */
689 : 7411 : TSCurrentConfigCache = InvalidOid;
690 : 7411 : }
|