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