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