Age Owner Branch data TLA 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-2025, 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
5135 tgl@sss.pgh.pa.us 94 :CBC 1054 : InvalidateTSCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
95 : : {
6591 96 : 1054 : HTAB *hash = (HTAB *) DatumGetPointer(arg);
97 : : HASH_SEQ_STATUS status;
98 : : TSAnyCacheEntry *entry;
99 : :
100 : 1054 : hash_seq_init(&status, hash);
101 [ + + ]: 3626 : while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL)
102 : 2572 : entry->isvalid = false;
103 : :
104 : : /* Also invalidate the current-config cache if it's pg_ts_config */
105 [ + + ]: 1054 : if (hash == TSConfigCacheHash)
106 : 942 : TSCurrentConfigCache = InvalidOid;
107 : 1054 : }
108 : :
109 : : /*
110 : : * Fetch parser cache entry
111 : : */
112 : : TSParserCacheEntry *
113 : 7053 : lookup_ts_parser_cache(Oid prsId)
114 : : {
115 : : TSParserCacheEntry *entry;
116 : :
117 [ + + ]: 7053 : if (TSParserCacheHash == NULL)
118 : : {
119 : : /* First time through: initialize the hash table */
120 : : HASHCTL ctl;
121 : :
122 : 115 : ctl.keysize = sizeof(Oid);
123 : 115 : ctl.entrysize = sizeof(TSParserCacheEntry);
124 : 115 : TSParserCacheHash = hash_create("Tsearch parser cache", 4,
125 : : &ctl, HASH_ELEM | HASH_BLOBS);
126 : : /* Flush cache on pg_ts_parser changes */
127 : 115 : CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack,
128 : : PointerGetDatum(TSParserCacheHash));
129 : :
130 : : /* Also make sure CacheMemoryContext exists */
5732 131 [ - + ]: 115 : if (!CacheMemoryContext)
5732 tgl@sss.pgh.pa.us 132 :UBC 0 : CreateCacheMemoryContext();
133 : : }
134 : :
135 : : /* Check single-entry cache */
6591 tgl@sss.pgh.pa.us 136 [ + + + - ]:CBC 7053 : if (lastUsedParser && lastUsedParser->prsId == prsId &&
137 [ + - ]: 6938 : lastUsedParser->isvalid)
138 : 6938 : return lastUsedParser;
139 : :
140 : : /* Try to look up an existing entry */
141 : 115 : entry = (TSParserCacheEntry *) hash_search(TSParserCacheHash,
142 : : &prsId,
143 : : HASH_FIND, NULL);
144 [ - + - - ]: 115 : 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 : :
5683 rhaas@postgresql.org 153 : 115 : tp = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId));
6591 tgl@sss.pgh.pa.us 154 [ - + ]: 115 : if (!HeapTupleIsValid(tp))
6591 tgl@sss.pgh.pa.us 155 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for text search parser %u",
156 : : prsId);
6591 tgl@sss.pgh.pa.us 157 :CBC 115 : prs = (Form_pg_ts_parser) GETSTRUCT(tp);
158 : :
159 : : /*
160 : : * Sanity checks
161 : : */
162 [ - + ]: 115 : if (!OidIsValid(prs->prsstart))
6591 tgl@sss.pgh.pa.us 163 [ # # ]:UBC 0 : elog(ERROR, "text search parser %u has no prsstart method", prsId);
6591 tgl@sss.pgh.pa.us 164 [ - + ]:CBC 115 : if (!OidIsValid(prs->prstoken))
6591 tgl@sss.pgh.pa.us 165 [ # # ]:UBC 0 : elog(ERROR, "text search parser %u has no prstoken method", prsId);
6591 tgl@sss.pgh.pa.us 166 [ - + ]:CBC 115 : if (!OidIsValid(prs->prsend))
6591 tgl@sss.pgh.pa.us 167 [ # # ]:UBC 0 : elog(ERROR, "text search parser %u has no prsend method", prsId);
168 : :
6591 tgl@sss.pgh.pa.us 169 [ + - ]:CBC 115 : if (entry == NULL)
170 : : {
171 : : bool found;
172 : :
173 : : /* Now make the cache entry */
174 : : entry = (TSParserCacheEntry *)
943 peter@eisentraut.org 175 : 115 : hash_search(TSParserCacheHash, &prsId, HASH_ENTER, &found);
6591 tgl@sss.pgh.pa.us 176 [ - + ]: 115 : Assert(!found); /* it wasn't there a moment ago */
177 : : }
178 : :
179 [ + - + - : 3335 : MemSet(entry, 0, sizeof(TSParserCacheEntry));
+ - + - +
+ ]
180 : 115 : entry->prsId = prsId;
181 : 115 : entry->startOid = prs->prsstart;
182 : 115 : entry->tokenOid = prs->prstoken;
183 : 115 : entry->endOid = prs->prsend;
184 : 115 : entry->headlineOid = prs->prsheadline;
185 : 115 : entry->lextypeOid = prs->prslextype;
186 : :
187 : 115 : ReleaseSysCache(tp);
188 : :
189 : 115 : fmgr_info_cxt(entry->startOid, &entry->prsstart, CacheMemoryContext);
190 : 115 : fmgr_info_cxt(entry->tokenOid, &entry->prstoken, CacheMemoryContext);
191 : 115 : fmgr_info_cxt(entry->endOid, &entry->prsend, CacheMemoryContext);
192 [ + - ]: 115 : if (OidIsValid(entry->headlineOid))
193 : 115 : fmgr_info_cxt(entry->headlineOid, &entry->prsheadline,
194 : : CacheMemoryContext);
195 : :
196 : 115 : entry->isvalid = true;
197 : : }
198 : :
199 : 115 : lastUsedParser = entry;
200 : :
201 : 115 : return entry;
202 : : }
203 : :
204 : : /*
205 : : * Fetch dictionary cache entry
206 : : */
207 : : TSDictionaryCacheEntry *
208 : 7572 : lookup_ts_dictionary_cache(Oid dictId)
209 : : {
210 : : TSDictionaryCacheEntry *entry;
211 : :
212 [ + + ]: 7572 : if (TSDictionaryCacheHash == NULL)
213 : : {
214 : : /* First time through: initialize the hash table */
215 : : HASHCTL ctl;
216 : :
217 : 23 : ctl.keysize = sizeof(Oid);
218 : 23 : ctl.entrysize = sizeof(TSDictionaryCacheEntry);
219 : 23 : 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 : 23 : CacheRegisterSyscacheCallback(TSDICTOID, InvalidateTSCacheCallBack,
223 : : PointerGetDatum(TSDictionaryCacheHash));
224 : 23 : CacheRegisterSyscacheCallback(TSTEMPLATEOID, InvalidateTSCacheCallBack,
225 : : PointerGetDatum(TSDictionaryCacheHash));
226 : :
227 : : /* Also make sure CacheMemoryContext exists */
5732 228 [ - + ]: 23 : if (!CacheMemoryContext)
5732 tgl@sss.pgh.pa.us 229 :UBC 0 : CreateCacheMemoryContext();
230 : : }
231 : :
232 : : /* Check single-entry cache */
6591 tgl@sss.pgh.pa.us 233 [ + + + + ]:CBC 7572 : if (lastUsedDictionary && lastUsedDictionary->dictId == dictId &&
234 [ + + ]: 6307 : lastUsedDictionary->isvalid)
235 : 6289 : return lastUsedDictionary;
236 : :
237 : : /* Try to look up an existing entry */
238 : 1283 : entry = (TSDictionaryCacheEntry *) hash_search(TSDictionaryCacheHash,
239 : : &dictId,
240 : : HASH_FIND, NULL);
241 [ + + + + ]: 1283 : 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 : :
5683 rhaas@postgresql.org 253 : 86 : tpdict = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
6591 tgl@sss.pgh.pa.us 254 [ - + ]: 86 : if (!HeapTupleIsValid(tpdict))
6591 tgl@sss.pgh.pa.us 255 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for text search dictionary %u",
256 : : dictId);
6591 tgl@sss.pgh.pa.us 257 :CBC 86 : dict = (Form_pg_ts_dict) GETSTRUCT(tpdict);
258 : :
259 : : /*
260 : : * Sanity checks
261 : : */
262 [ - + ]: 86 : if (!OidIsValid(dict->dicttemplate))
6591 tgl@sss.pgh.pa.us 263 [ # # ]:UBC 0 : elog(ERROR, "text search dictionary %u has no template", dictId);
264 : :
265 : : /*
266 : : * Retrieve dictionary's template
267 : : */
5683 rhaas@postgresql.org 268 :CBC 86 : tptmpl = SearchSysCache1(TSTEMPLATEOID,
269 : : ObjectIdGetDatum(dict->dicttemplate));
6591 tgl@sss.pgh.pa.us 270 [ - + ]: 86 : if (!HeapTupleIsValid(tptmpl))
6591 tgl@sss.pgh.pa.us 271 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for text search template %u",
272 : : dict->dicttemplate);
6591 tgl@sss.pgh.pa.us 273 :CBC 86 : template = (Form_pg_ts_template) GETSTRUCT(tptmpl);
274 : :
275 : : /*
276 : : * Sanity checks
277 : : */
278 [ - + ]: 86 : if (!OidIsValid(template->tmpllexize))
6591 tgl@sss.pgh.pa.us 279 [ # # ]:UBC 0 : elog(ERROR, "text search template %u has no lexize method",
280 : : template->tmpllexize);
281 : :
6591 tgl@sss.pgh.pa.us 282 [ + + ]:CBC 86 : if (entry == NULL)
283 : : {
284 : : bool found;
285 : :
286 : : /* Now make the cache entry */
287 : : entry = (TSDictionaryCacheEntry *)
288 : 53 : hash_search(TSDictionaryCacheHash,
289 : : &dictId,
290 : : HASH_ENTER, &found);
291 [ - + ]: 53 : Assert(!found); /* it wasn't there a moment ago */
292 : :
293 : : /* Create private memory context the first time through */
2720 294 : 53 : saveCtx = AllocSetContextCreate(CacheMemoryContext,
295 : : "TS dictionary",
296 : : ALLOCSET_SMALL_SIZES);
2710 peter_e@gmx.net 297 : 53 : MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname));
298 : : }
299 : : else
300 : : {
301 : : /* Clear the existing entry's private context */
6591 tgl@sss.pgh.pa.us 302 : 33 : saveCtx = entry->dictCtx;
303 : : /* Don't let context's ident pointer dangle while we reset it */
2720 304 : 33 : MemoryContextSetIdentifier(saveCtx, NULL);
305 : 33 : MemoryContextReset(saveCtx);
2710 peter_e@gmx.net 306 : 33 : MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname));
307 : : }
308 : :
6591 tgl@sss.pgh.pa.us 309 [ + - + - : 946 : MemSet(entry, 0, sizeof(TSDictionaryCacheEntry));
+ - + - +
+ ]
310 : 86 : entry->dictId = dictId;
311 : 86 : entry->dictCtx = saveCtx;
312 : :
313 : 86 : entry->lexizeOid = template->tmpllexize;
314 : :
315 [ + - ]: 86 : 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. 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 : : */
6590 328 : 86 : oldcontext = MemoryContextSwitchTo(entry->dictCtx);
329 : :
6591 330 : 86 : opt = SysCacheGetAttr(TSDICTOID, tpdict,
331 : : Anum_pg_ts_dict_dictinitoption,
332 : : &isnull);
333 [ + + ]: 86 : if (isnull)
6590 334 : 17 : dictoptions = NIL;
335 : : else
336 : 69 : dictoptions = deserialize_deflist(opt);
337 : :
338 : 86 : entry->dictData =
339 : 86 : DatumGetPointer(OidFunctionCall1(template->tmplinit,
340 : : PointerGetDatum(dictoptions)));
341 : :
342 : 86 : MemoryContextSwitchTo(oldcontext);
343 : : }
344 : :
6591 345 : 86 : ReleaseSysCache(tptmpl);
346 : 86 : ReleaseSysCache(tpdict);
347 : :
348 : 86 : fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
349 : :
350 : 86 : entry->isvalid = true;
351 : : }
352 : :
353 : 1283 : lastUsedDictionary = entry;
354 : :
355 : 1283 : return entry;
356 : : }
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 : 19 : init_ts_config_cache(void)
365 : : {
366 : : HASHCTL ctl;
367 : :
368 : 19 : ctl.keysize = sizeof(Oid);
369 : 19 : ctl.entrysize = sizeof(TSConfigCacheEntry);
370 : 19 : 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 : 19 : CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack,
374 : : PointerGetDatum(TSConfigCacheHash));
375 : 19 : CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack,
376 : : PointerGetDatum(TSConfigCacheHash));
377 : :
378 : : /* Also make sure CacheMemoryContext exists */
5732 379 [ - + ]: 19 : if (!CacheMemoryContext)
5732 tgl@sss.pgh.pa.us 380 :UBC 0 : CreateCacheMemoryContext();
6591 tgl@sss.pgh.pa.us 381 :CBC 19 : }
382 : :
383 : : /*
384 : : * Fetch configuration cache entry
385 : : */
386 : : TSConfigCacheEntry *
387 : 2475 : lookup_ts_config_cache(Oid cfgId)
388 : : {
389 : : TSConfigCacheEntry *entry;
390 : :
391 [ + + ]: 2475 : if (TSConfigCacheHash == NULL)
392 : : {
393 : : /* First time through: initialize the hash table */
394 : 13 : init_ts_config_cache();
395 : : }
396 : :
397 : : /* Check single-entry cache */
398 [ + + + + ]: 2475 : if (lastUsedConfig && lastUsedConfig->cfgId == cfgId &&
399 [ + + ]: 2402 : lastUsedConfig->isvalid)
400 : 2396 : return lastUsedConfig;
401 : :
402 : : /* Try to look up an existing entry */
403 : 79 : entry = (TSConfigCacheEntry *) hash_search(TSConfigCacheHash,
404 : : &cfgId,
405 : : HASH_FIND, NULL);
406 [ + + + + ]: 79 : 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 : : HeapTuple tp;
413 : : Form_pg_ts_config cfg;
414 : : Relation maprel;
415 : : Relation mapidx;
416 : : ScanKeyData mapskey;
417 : : SysScanDesc mapscan;
418 : : HeapTuple maptup;
419 : : ListDictionary maplists[MAXTOKENTYPE + 1];
420 : : Oid mapdicts[MAXDICTSPERTT];
421 : : int maxtokentype;
422 : : int ndicts;
423 : : int i;
424 : :
5683 rhaas@postgresql.org 425 : 46 : tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
6591 tgl@sss.pgh.pa.us 426 [ - + ]: 46 : if (!HeapTupleIsValid(tp))
6591 tgl@sss.pgh.pa.us 427 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for text search configuration %u",
428 : : cfgId);
6591 tgl@sss.pgh.pa.us 429 :CBC 46 : cfg = (Form_pg_ts_config) GETSTRUCT(tp);
430 : :
431 : : /*
432 : : * Sanity checks
433 : : */
434 [ - + ]: 46 : if (!OidIsValid(cfg->cfgparser))
6591 tgl@sss.pgh.pa.us 435 [ # # ]:UBC 0 : elog(ERROR, "text search configuration %u has no parser", cfgId);
436 : :
6591 tgl@sss.pgh.pa.us 437 [ + + ]:CBC 46 : if (entry == NULL)
438 : : {
439 : : bool found;
440 : :
441 : : /* Now make the cache entry */
442 : : entry = (TSConfigCacheEntry *)
443 : 40 : hash_search(TSConfigCacheHash,
444 : : &cfgId,
445 : : HASH_ENTER, &found);
446 [ - + ]: 40 : Assert(!found); /* it wasn't there a moment ago */
447 : : }
448 : : else
449 : : {
450 : : /* Cleanup old contents */
451 [ + - ]: 6 : if (entry->map)
452 : : {
453 [ + + ]: 144 : for (i = 0; i < entry->lenmap; i++)
454 [ + + ]: 138 : if (entry->map[i].dictIds)
455 : 114 : pfree(entry->map[i].dictIds);
456 : 6 : pfree(entry->map);
457 : : }
458 : : }
459 : :
460 [ + - + - : 184 : MemSet(entry, 0, sizeof(TSConfigCacheEntry));
+ - + - +
+ ]
461 : 46 : entry->cfgId = cfgId;
462 : 46 : entry->prsId = cfg->cfgparser;
463 : :
464 : 46 : 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 [ + - + - : 46 : MemSet(maplists, 0, sizeof(maplists));
+ - - + -
- ]
474 : 46 : maxtokentype = 0;
475 : 46 : ndicts = 0;
476 : :
477 : 46 : ScanKeyInit(&mapskey,
478 : : Anum_pg_ts_config_map_mapcfg,
479 : : BTEqualStrategyNumber, F_OIDEQ,
480 : : ObjectIdGetDatum(cfgId));
481 : :
2420 andres@anarazel.de 482 : 46 : maprel = table_open(TSConfigMapRelationId, AccessShareLock);
6591 tgl@sss.pgh.pa.us 483 : 46 : mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
6356 484 : 46 : mapscan = systable_beginscan_ordered(maprel, mapidx,
485 : : NULL, 1, &mapskey);
486 : :
487 [ + + ]: 1037 : while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL)
488 : : {
6591 489 : 991 : Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
6505 bruce@momjian.us 490 : 991 : int toktype = cfgmap->maptokentype;
491 : :
6591 tgl@sss.pgh.pa.us 492 [ + - - + ]: 991 : if (toktype <= 0 || toktype > MAXTOKENTYPE)
6591 tgl@sss.pgh.pa.us 493 [ # # ]:UBC 0 : elog(ERROR, "maptokentype value %d is out of range", toktype);
6591 tgl@sss.pgh.pa.us 494 [ - + ]:CBC 991 : if (toktype < maxtokentype)
6591 tgl@sss.pgh.pa.us 495 [ # # ]:UBC 0 : elog(ERROR, "maptokentype entries are out of order");
6591 tgl@sss.pgh.pa.us 496 [ + + ]:CBC 991 : if (toktype > maxtokentype)
497 : : {
498 : : /* starting a new token type, but first save the prior data */
499 [ + + ]: 856 : if (ndicts > 0)
500 : : {
501 : 810 : maplists[maxtokentype].len = ndicts;
502 : 810 : maplists[maxtokentype].dictIds = (Oid *)
503 : 810 : MemoryContextAlloc(CacheMemoryContext,
504 : : sizeof(Oid) * ndicts);
505 : 810 : memcpy(maplists[maxtokentype].dictIds, mapdicts,
506 : : sizeof(Oid) * ndicts);
507 : : }
508 : 856 : maxtokentype = toktype;
509 : 856 : mapdicts[0] = cfgmap->mapdict;
510 : 856 : ndicts = 1;
511 : : }
512 : : else
513 : : {
514 : : /* continuing data for current token type */
515 [ - + ]: 135 : if (ndicts >= MAXDICTSPERTT)
6591 tgl@sss.pgh.pa.us 516 [ # # ]:UBC 0 : elog(ERROR, "too many pg_ts_config_map entries for one token type");
6591 tgl@sss.pgh.pa.us 517 :CBC 135 : mapdicts[ndicts++] = cfgmap->mapdict;
518 : : }
519 : : }
520 : :
6356 521 : 46 : systable_endscan_ordered(mapscan);
6591 522 : 46 : index_close(mapidx, AccessShareLock);
2420 andres@anarazel.de 523 : 46 : table_close(maprel, AccessShareLock);
524 : :
6591 tgl@sss.pgh.pa.us 525 [ + - ]: 46 : if (ndicts > 0)
526 : : {
527 : : /* save the last token type's dictionaries */
528 : 46 : maplists[maxtokentype].len = ndicts;
529 : 46 : maplists[maxtokentype].dictIds = (Oid *)
530 : 46 : MemoryContextAlloc(CacheMemoryContext,
531 : : sizeof(Oid) * ndicts);
532 : 46 : memcpy(maplists[maxtokentype].dictIds, mapdicts,
533 : : sizeof(Oid) * ndicts);
534 : : /* and save the overall map */
535 : 46 : entry->lenmap = maxtokentype + 1;
536 : 46 : entry->map = (ListDictionary *)
537 : 46 : MemoryContextAlloc(CacheMemoryContext,
538 : 46 : sizeof(ListDictionary) * entry->lenmap);
539 : 46 : memcpy(entry->map, maplists,
540 : 46 : sizeof(ListDictionary) * entry->lenmap);
541 : : }
542 : :
543 : 46 : entry->isvalid = true;
544 : : }
545 : :
546 : 79 : lastUsedConfig = entry;
547 : :
548 : 79 : return entry;
549 : : }
550 : :
551 : :
552 : : /*---------------------------------------------------
553 : : * GUC variable "default_text_search_config"
554 : : *---------------------------------------------------
555 : : */
556 : :
557 : : Oid
558 : 195 : getTSCurrentConfig(bool emitError)
559 : : {
560 : : List *namelist;
561 : :
562 : : /* if we have a cached value, return it */
563 [ + + ]: 195 : if (OidIsValid(TSCurrentConfigCache))
564 : 180 : return TSCurrentConfigCache;
565 : :
566 : : /* fail if GUC hasn't been set up yet */
567 [ + - - + ]: 15 : if (TSCurrentConfig == NULL || *TSCurrentConfig == '\0')
568 : : {
6591 tgl@sss.pgh.pa.us 569 [ # # ]:UBC 0 : if (emitError)
570 [ # # ]: 0 : elog(ERROR, "text search configuration isn't set");
571 : : else
572 : 0 : return InvalidOid;
573 : : }
574 : :
6591 tgl@sss.pgh.pa.us 575 [ + + ]:CBC 15 : if (TSConfigCacheHash == NULL)
576 : : {
577 : : /* First time through: initialize the tsconfig inval callback */
578 : 6 : init_ts_config_cache();
579 : : }
580 : :
581 : : /* Look up the config */
984 582 [ + - ]: 15 : if (emitError)
583 : : {
584 : 15 : namelist = stringToQualifiedNameList(TSCurrentConfig, NULL);
585 : 15 : TSCurrentConfigCache = get_ts_config_oid(namelist, false);
586 : : }
587 : : else
588 : : {
984 tgl@sss.pgh.pa.us 589 :UBC 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 : : }
598 : :
6591 tgl@sss.pgh.pa.us 599 :CBC 15 : return TSCurrentConfigCache;
600 : : }
601 : :
602 : : /* GUC check_hook for default_text_search_config */
603 : : bool
1089 604 : 5472 : 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 : : */
2280 andres@anarazel.de 611 [ + + + - ]: 5472 : if (IsTransactionState() && MyDatabaseId != InvalidOid)
612 : : {
984 tgl@sss.pgh.pa.us 613 : 2779 : ErrorSaveContext escontext = {T_ErrorSaveContext};
614 : : List *namelist;
615 : : Oid cfgId;
616 : : HeapTuple tuple;
617 : : Form_pg_ts_config cfg;
618 : : char *buf;
619 : :
620 : 2779 : namelist = stringToQualifiedNameList(*newval,
621 : : (Node *) &escontext);
622 [ + - ]: 2779 : if (namelist != NIL)
623 : 2779 : cfgId = get_ts_config_oid(namelist, true);
624 : : else
984 tgl@sss.pgh.pa.us 625 :UBC 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 : : */
6591 tgl@sss.pgh.pa.us 631 [ + + ]:CBC 2779 : if (!OidIsValid(cfgId))
632 : : {
4968 heikki.linnakangas@i 633 [ + + ]: 13 : if (source == PGC_S_TEST)
634 : : {
635 [ + + ]: 7 : ereport(NOTICE,
636 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
637 : : errmsg("text search configuration \"%s\" does not exist", *newval)));
638 : 13 : return true;
639 : : }
640 : : else
641 : 6 : 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 : : */
5683 rhaas@postgresql.org 648 : 2766 : tuple = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
6591 tgl@sss.pgh.pa.us 649 [ - + ]: 2766 : if (!HeapTupleIsValid(tuple))
6591 tgl@sss.pgh.pa.us 650 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for text search configuration %u",
651 : : cfgId);
6591 tgl@sss.pgh.pa.us 652 :CBC 2766 : cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
653 : :
654 : 2766 : buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace),
655 : 2766 : NameStr(cfg->cfgname));
656 : :
657 : 2766 : ReleaseSysCache(tuple);
658 : :
659 : : /* GUC wants it guc_malloc'd not palloc'd */
1058 660 : 2766 : guc_free(*newval);
661 : 2766 : *newval = guc_strdup(LOG, buf);
6591 662 : 2766 : pfree(buf);
4612 663 [ - + ]: 2766 : if (!*newval)
5266 tgl@sss.pgh.pa.us 664 :UBC 0 : return false;
665 : : }
666 : :
5266 tgl@sss.pgh.pa.us 667 :CBC 5459 : return true;
668 : : }
669 : :
670 : : /* GUC assign_hook for default_text_search_config */
671 : : void
1089 672 : 5456 : assign_default_text_search_config(const char *newval, void *extra)
673 : : {
674 : : /* Just reset the cache to force a lookup on first use */
5266 675 : 5456 : TSCurrentConfigCache = InvalidOid;
6591 676 : 5456 : }
|