1 /*-------------------------------------------------------------------------
4 * Tsearch related object caches.
6 * Tsearch performance is very sensitive to performance of parsers,
7 * dictionaries and mapping, so lookups should be cached as much
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.
20 * Copyright (c) 2006-2016, PostgreSQL Global Development Group
23 * src/backend/utils/cache/ts_cache.c
25 *-------------------------------------------------------------------------
29 #include "access/genam.h"
30 #include "access/heapam.h"
31 #include "access/htup_details.h"
32 #include "access/xact.h"
33 #include "catalog/indexing.h"
34 #include "catalog/namespace.h"
35 #include "catalog/pg_ts_config.h"
36 #include "catalog/pg_ts_config_map.h"
37 #include "catalog/pg_ts_dict.h"
38 #include "catalog/pg_ts_parser.h"
39 #include "catalog/pg_ts_template.h"
40 #include "commands/defrem.h"
41 #include "tsearch/ts_cache.h"
42 #include "utils/builtins.h"
43 #include "utils/catcache.h"
44 #include "utils/fmgroids.h"
45 #include "utils/inval.h"
46 #include "utils/lsyscache.h"
47 #include "utils/memutils.h"
48 #include "utils/syscache.h"
49 #include "utils/tqual.h"
53 * MAXTOKENTYPE/MAXDICTSPERTT are arbitrary limits on the workspace size
54 * used in lookup_ts_config_cache(). We could avoid hardwiring a limit
55 * by making the workspace dynamically enlargeable, but it seems unlikely
56 * to be worth the trouble.
58 #define MAXTOKENTYPE 256
59 #define MAXDICTSPERTT 100
62 static HTAB *TSParserCacheHash = NULL;
63 static TSParserCacheEntry *lastUsedParser = NULL;
65 static HTAB *TSDictionaryCacheHash = NULL;
66 static TSDictionaryCacheEntry *lastUsedDictionary = NULL;
68 static HTAB *TSConfigCacheHash = NULL;
69 static TSConfigCacheEntry *lastUsedConfig = NULL;
72 * GUC default_text_search_config, and a cache of the current config's OID
74 char *TSCurrentConfig = NULL;
76 static Oid TSCurrentConfigCache = InvalidOid;
80 * We use this syscache callback to detect when a visible change to a TS
81 * catalog entry has been made, by either our own backend or another one.
83 * In principle we could just flush the specific cache entry that changed,
84 * but given that TS configuration changes are probably infrequent, it
85 * doesn't seem worth the trouble to determine that; we just flush all the
86 * entries of the related hash table.
88 * We can use the same function for all TS caches by passing the hash
89 * table address as the "arg".
92 InvalidateTSCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
94 HTAB *hash = (HTAB *) DatumGetPointer(arg);
95 HASH_SEQ_STATUS status;
96 TSAnyCacheEntry *entry;
98 hash_seq_init(&status, hash);
99 while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL)
100 entry->isvalid = false;
102 /* Also invalidate the current-config cache if it's pg_ts_config */
103 if (hash == TSConfigCacheHash)
104 TSCurrentConfigCache = InvalidOid;
108 * Fetch parser cache entry
111 lookup_ts_parser_cache(Oid prsId)
113 TSParserCacheEntry *entry;
115 if (TSParserCacheHash == NULL)
117 /* First time through: initialize the hash table */
120 MemSet(&ctl, 0, sizeof(ctl));
121 ctl.keysize = sizeof(Oid);
122 ctl.entrysize = sizeof(TSParserCacheEntry);
123 TSParserCacheHash = hash_create("Tsearch parser cache", 4,
124 &ctl, HASH_ELEM | HASH_BLOBS);
125 /* Flush cache on pg_ts_parser changes */
126 CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack,
127 PointerGetDatum(TSParserCacheHash));
129 /* Also make sure CacheMemoryContext exists */
130 if (!CacheMemoryContext)
131 CreateCacheMemoryContext();
134 /* Check single-entry cache */
135 if (lastUsedParser && lastUsedParser->prsId == prsId &&
136 lastUsedParser->isvalid)
137 return lastUsedParser;
139 /* Try to look up an existing entry */
140 entry = (TSParserCacheEntry *) hash_search(TSParserCacheHash,
143 if (entry == NULL || !entry->isvalid)
146 * If we didn't find one, we want to make one. But first look up the
147 * object to be sure the OID is real.
150 Form_pg_ts_parser prs;
152 tp = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId));
153 if (!HeapTupleIsValid(tp))
154 elog(ERROR, "cache lookup failed for text search parser %u",
156 prs = (Form_pg_ts_parser) GETSTRUCT(tp);
161 if (!OidIsValid(prs->prsstart))
162 elog(ERROR, "text search parser %u has no prsstart method", prsId);
163 if (!OidIsValid(prs->prstoken))
164 elog(ERROR, "text search parser %u has no prstoken method", prsId);
165 if (!OidIsValid(prs->prsend))
166 elog(ERROR, "text search parser %u has no prsend method", prsId);
172 /* Now make the cache entry */
173 entry = (TSParserCacheEntry *)
174 hash_search(TSParserCacheHash,
177 Assert(!found); /* it wasn't there a moment ago */
180 MemSet(entry, 0, sizeof(TSParserCacheEntry));
181 entry->prsId = prsId;
182 entry->startOid = prs->prsstart;
183 entry->tokenOid = prs->prstoken;
184 entry->endOid = prs->prsend;
185 entry->headlineOid = prs->prsheadline;
186 entry->lextypeOid = prs->prslextype;
190 fmgr_info_cxt(entry->startOid, &entry->prsstart, CacheMemoryContext);
191 fmgr_info_cxt(entry->tokenOid, &entry->prstoken, CacheMemoryContext);
192 fmgr_info_cxt(entry->endOid, &entry->prsend, CacheMemoryContext);
193 if (OidIsValid(entry->headlineOid))
194 fmgr_info_cxt(entry->headlineOid, &entry->prsheadline,
197 entry->isvalid = true;
200 lastUsedParser = entry;
206 * Fetch dictionary cache entry
208 TSDictionaryCacheEntry *
209 lookup_ts_dictionary_cache(Oid dictId)
211 TSDictionaryCacheEntry *entry;
213 if (TSDictionaryCacheHash == NULL)
215 /* First time through: initialize the hash table */
218 MemSet(&ctl, 0, sizeof(ctl));
219 ctl.keysize = sizeof(Oid);
220 ctl.entrysize = sizeof(TSDictionaryCacheEntry);
221 TSDictionaryCacheHash = hash_create("Tsearch dictionary cache", 8,
222 &ctl, HASH_ELEM | HASH_BLOBS);
223 /* Flush cache on pg_ts_dict and pg_ts_template changes */
224 CacheRegisterSyscacheCallback(TSDICTOID, InvalidateTSCacheCallBack,
225 PointerGetDatum(TSDictionaryCacheHash));
226 CacheRegisterSyscacheCallback(TSTEMPLATEOID, InvalidateTSCacheCallBack,
227 PointerGetDatum(TSDictionaryCacheHash));
229 /* Also make sure CacheMemoryContext exists */
230 if (!CacheMemoryContext)
231 CreateCacheMemoryContext();
234 /* Check single-entry cache */
235 if (lastUsedDictionary && lastUsedDictionary->dictId == dictId &&
236 lastUsedDictionary->isvalid)
237 return lastUsedDictionary;
239 /* Try to look up an existing entry */
240 entry = (TSDictionaryCacheEntry *) hash_search(TSDictionaryCacheHash,
243 if (entry == NULL || !entry->isvalid)
246 * If we didn't find one, we want to make one. But first look up the
247 * object to be sure the OID is real.
251 Form_pg_ts_dict dict;
252 Form_pg_ts_template template;
253 MemoryContext saveCtx;
255 tpdict = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
256 if (!HeapTupleIsValid(tpdict))
257 elog(ERROR, "cache lookup failed for text search dictionary %u",
259 dict = (Form_pg_ts_dict) GETSTRUCT(tpdict);
264 if (!OidIsValid(dict->dicttemplate))
265 elog(ERROR, "text search dictionary %u has no template", dictId);
268 * Retrieve dictionary's template
270 tptmpl = SearchSysCache1(TSTEMPLATEOID,
271 ObjectIdGetDatum(dict->dicttemplate));
272 if (!HeapTupleIsValid(tptmpl))
273 elog(ERROR, "cache lookup failed for text search template %u",
275 template = (Form_pg_ts_template) GETSTRUCT(tptmpl);
280 if (!OidIsValid(template->tmpllexize))
281 elog(ERROR, "text search template %u has no lexize method",
282 template->tmpllexize);
288 /* Now make the cache entry */
289 entry = (TSDictionaryCacheEntry *)
290 hash_search(TSDictionaryCacheHash,
293 Assert(!found); /* it wasn't there a moment ago */
295 /* Create private memory context the first time through */
296 saveCtx = AllocSetContextCreate(CacheMemoryContext,
297 NameStr(dict->dictname),
298 ALLOCSET_SMALL_MINSIZE,
299 ALLOCSET_SMALL_INITSIZE,
300 ALLOCSET_SMALL_MAXSIZE);
304 /* Clear the existing entry's private context */
305 saveCtx = entry->dictCtx;
306 MemoryContextResetAndDeleteChildren(saveCtx);
309 MemSet(entry, 0, sizeof(TSDictionaryCacheEntry));
310 entry->dictId = dictId;
311 entry->dictCtx = saveCtx;
313 entry->lexizeOid = template->tmpllexize;
315 if (OidIsValid(template->tmplinit))
320 MemoryContext oldcontext;
323 * Init method runs in dictionary's private memory context, and we
324 * make sure the options are stored there too
326 oldcontext = MemoryContextSwitchTo(entry->dictCtx);
328 opt = SysCacheGetAttr(TSDICTOID, tpdict,
329 Anum_pg_ts_dict_dictinitoption,
334 dictoptions = deserialize_deflist(opt);
337 DatumGetPointer(OidFunctionCall1(template->tmplinit,
338 PointerGetDatum(dictoptions)));
340 MemoryContextSwitchTo(oldcontext);
343 ReleaseSysCache(tptmpl);
344 ReleaseSysCache(tpdict);
346 fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
348 entry->isvalid = true;
351 lastUsedDictionary = entry;
357 * Initialize config cache and prepare callbacks. This is split out of
358 * lookup_ts_config_cache because we need to activate the callback before
359 * caching TSCurrentConfigCache, too.
362 init_ts_config_cache(void)
366 MemSet(&ctl, 0, sizeof(ctl));
367 ctl.keysize = sizeof(Oid);
368 ctl.entrysize = sizeof(TSConfigCacheEntry);
369 TSConfigCacheHash = hash_create("Tsearch configuration cache", 16,
370 &ctl, HASH_ELEM | HASH_BLOBS);
371 /* Flush cache on pg_ts_config and pg_ts_config_map changes */
372 CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack,
373 PointerGetDatum(TSConfigCacheHash));
374 CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack,
375 PointerGetDatum(TSConfigCacheHash));
377 /* Also make sure CacheMemoryContext exists */
378 if (!CacheMemoryContext)
379 CreateCacheMemoryContext();
383 * Fetch configuration cache entry
386 lookup_ts_config_cache(Oid cfgId)
388 TSConfigCacheEntry *entry;
390 if (TSConfigCacheHash == NULL)
392 /* First time through: initialize the hash table */
393 init_ts_config_cache();
396 /* Check single-entry cache */
397 if (lastUsedConfig && lastUsedConfig->cfgId == cfgId &&
398 lastUsedConfig->isvalid)
399 return lastUsedConfig;
401 /* Try to look up an existing entry */
402 entry = (TSConfigCacheEntry *) hash_search(TSConfigCacheHash,
405 if (entry == NULL || !entry->isvalid)
408 * If we didn't find one, we want to make one. But first look up the
409 * object to be sure the OID is real.
412 Form_pg_ts_config cfg;
418 ListDictionary maplists[MAXTOKENTYPE + 1];
419 Oid mapdicts[MAXDICTSPERTT];
424 tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
425 if (!HeapTupleIsValid(tp))
426 elog(ERROR, "cache lookup failed for text search configuration %u",
428 cfg = (Form_pg_ts_config) GETSTRUCT(tp);
433 if (!OidIsValid(cfg->cfgparser))
434 elog(ERROR, "text search configuration %u has no parser", cfgId);
440 /* Now make the cache entry */
441 entry = (TSConfigCacheEntry *)
442 hash_search(TSConfigCacheHash,
445 Assert(!found); /* it wasn't there a moment ago */
449 /* Cleanup old contents */
452 for (i = 0; i < entry->lenmap; i++)
453 if (entry->map[i].dictIds)
454 pfree(entry->map[i].dictIds);
459 MemSet(entry, 0, sizeof(TSConfigCacheEntry));
460 entry->cfgId = cfgId;
461 entry->prsId = cfg->cfgparser;
466 * Scan pg_ts_config_map to gather dictionary list for each token type
468 * Because the index is on (mapcfg, maptokentype, mapseqno), we will
469 * see the entries in maptokentype order, and in mapseqno order for
470 * each token type, even though we didn't explicitly ask for that.
472 MemSet(maplists, 0, sizeof(maplists));
476 ScanKeyInit(&mapskey,
477 Anum_pg_ts_config_map_mapcfg,
478 BTEqualStrategyNumber, F_OIDEQ,
479 ObjectIdGetDatum(cfgId));
481 maprel = heap_open(TSConfigMapRelationId, AccessShareLock);
482 mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
483 mapscan = systable_beginscan_ordered(maprel, mapidx,
486 while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL)
488 Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
489 int toktype = cfgmap->maptokentype;
491 if (toktype <= 0 || toktype > MAXTOKENTYPE)
492 elog(ERROR, "maptokentype value %d is out of range", toktype);
493 if (toktype < maxtokentype)
494 elog(ERROR, "maptokentype entries are out of order");
495 if (toktype > maxtokentype)
497 /* starting a new token type, but first save the prior data */
500 maplists[maxtokentype].len = ndicts;
501 maplists[maxtokentype].dictIds = (Oid *)
502 MemoryContextAlloc(CacheMemoryContext,
503 sizeof(Oid) * ndicts);
504 memcpy(maplists[maxtokentype].dictIds, mapdicts,
505 sizeof(Oid) * ndicts);
507 maxtokentype = toktype;
508 mapdicts[0] = cfgmap->mapdict;
513 /* continuing data for current token type */
514 if (ndicts >= MAXDICTSPERTT)
515 elog(ERROR, "too many pg_ts_config_map entries for one token type");
516 mapdicts[ndicts++] = cfgmap->mapdict;
520 systable_endscan_ordered(mapscan);
521 index_close(mapidx, AccessShareLock);
522 heap_close(maprel, AccessShareLock);
526 /* save the last token type's dictionaries */
527 maplists[maxtokentype].len = ndicts;
528 maplists[maxtokentype].dictIds = (Oid *)
529 MemoryContextAlloc(CacheMemoryContext,
530 sizeof(Oid) * ndicts);
531 memcpy(maplists[maxtokentype].dictIds, mapdicts,
532 sizeof(Oid) * ndicts);
533 /* and save the overall map */
534 entry->lenmap = maxtokentype + 1;
535 entry->map = (ListDictionary *)
536 MemoryContextAlloc(CacheMemoryContext,
537 sizeof(ListDictionary) * entry->lenmap);
538 memcpy(entry->map, maplists,
539 sizeof(ListDictionary) * entry->lenmap);
542 entry->isvalid = true;
545 lastUsedConfig = entry;
551 /*---------------------------------------------------
552 * GUC variable "default_text_search_config"
553 *---------------------------------------------------
557 getTSCurrentConfig(bool emitError)
559 /* if we have a cached value, return it */
560 if (OidIsValid(TSCurrentConfigCache))
561 return TSCurrentConfigCache;
563 /* fail if GUC hasn't been set up yet */
564 if (TSCurrentConfig == NULL || *TSCurrentConfig == '\0')
567 elog(ERROR, "text search configuration isn't set");
572 if (TSConfigCacheHash == NULL)
574 /* First time through: initialize the tsconfig inval callback */
575 init_ts_config_cache();
578 /* Look up the config */
579 TSCurrentConfigCache =
580 get_ts_config_oid(stringToQualifiedNameList(TSCurrentConfig),
583 return TSCurrentConfigCache;
586 /* GUC check_hook for default_text_search_config */
588 check_TSCurrentConfig(char **newval, void **extra, GucSource source)
591 * If we aren't inside a transaction, we cannot do database access so
592 * cannot verify the config name. Must accept it on faith.
594 if (IsTransactionState())
598 Form_pg_ts_config cfg;
601 cfgId = get_ts_config_oid(stringToQualifiedNameList(*newval), true);
604 * When source == PGC_S_TEST, don't throw a hard error for a
605 * nonexistent configuration, only a NOTICE. See comments in guc.h.
607 if (!OidIsValid(cfgId))
609 if (source == PGC_S_TEST)
612 (errcode(ERRCODE_UNDEFINED_OBJECT),
613 errmsg("text search configuration \"%s\" does not exist", *newval)));
621 * Modify the actually stored value to be fully qualified, to ensure
622 * later changes of search_path don't affect it.
624 tuple = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
625 if (!HeapTupleIsValid(tuple))
626 elog(ERROR, "cache lookup failed for text search configuration %u",
628 cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
630 buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace),
631 NameStr(cfg->cfgname));
633 ReleaseSysCache(tuple);
635 /* GUC wants it malloc'd not palloc'd */
637 *newval = strdup(buf);
646 /* GUC assign_hook for default_text_search_config */
648 assign_TSCurrentConfig(const char *newval, void *extra)
650 /* Just reset the cache to force a lookup on first use */
651 TSCurrentConfigCache = InvalidOid;