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-2009, PostgreSQL Global Development Group
23 * $PostgreSQL: pgsql/src/backend/utils/cache/ts_cache.c,v 1.10 2009/12/27 18:55:52 tgl Exp $
25 *-------------------------------------------------------------------------
29 #include "access/genam.h"
30 #include "access/heapam.h"
31 #include "access/xact.h"
32 #include "catalog/indexing.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 "catalog/pg_type.h"
40 #include "commands/defrem.h"
41 #include "miscadmin.h"
42 #include "tsearch/ts_cache.h"
43 #include "utils/array.h"
44 #include "utils/builtins.h"
45 #include "utils/fmgroids.h"
46 #include "utils/inval.h"
47 #include "utils/lsyscache.h"
48 #include "utils/memutils.h"
49 #include "utils/syscache.h"
50 #include "utils/tqual.h"
54 * MAXTOKENTYPE/MAXDICTSPERTT are arbitrary limits on the workspace size
55 * used in lookup_ts_config_cache(). We could avoid hardwiring a limit
56 * by making the workspace dynamically enlargeable, but it seems unlikely
57 * to be worth the trouble.
59 #define MAXTOKENTYPE 256
60 #define MAXDICTSPERTT 100
63 static HTAB *TSParserCacheHash = NULL;
64 static TSParserCacheEntry *lastUsedParser = NULL;
66 static HTAB *TSDictionaryCacheHash = NULL;
67 static TSDictionaryCacheEntry *lastUsedDictionary = NULL;
69 static HTAB *TSConfigCacheHash = NULL;
70 static TSConfigCacheEntry *lastUsedConfig = NULL;
73 * GUC default_text_search_config, and a cache of the current config's OID
75 char *TSCurrentConfig = NULL;
77 static Oid TSCurrentConfigCache = InvalidOid;
81 * We use this syscache callback to detect when a visible change to a TS
82 * catalog entry has been made, by either our own backend or another one.
84 * In principle we could just flush the specific cache entry that changed,
85 * but given that TS configuration changes are probably infrequent, it
86 * doesn't seem worth the trouble to determine that; we just flush all the
87 * entries of the related hash table.
89 * We can use the same function for all TS caches by passing the hash
90 * table address as the "arg".
93 InvalidateTSCacheCallBack(Datum arg, int cacheid, ItemPointer tuplePtr)
95 HTAB *hash = (HTAB *) DatumGetPointer(arg);
96 HASH_SEQ_STATUS status;
97 TSAnyCacheEntry *entry;
99 hash_seq_init(&status, hash);
100 while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL)
101 entry->isvalid = false;
103 /* Also invalidate the current-config cache if it's pg_ts_config */
104 if (hash == TSConfigCacheHash)
105 TSCurrentConfigCache = InvalidOid;
109 * Fetch parser cache entry
112 lookup_ts_parser_cache(Oid prsId)
114 TSParserCacheEntry *entry;
116 if (TSParserCacheHash == NULL)
118 /* First time through: initialize the hash table */
121 MemSet(&ctl, 0, sizeof(ctl));
122 ctl.keysize = sizeof(Oid);
123 ctl.entrysize = sizeof(TSParserCacheEntry);
125 TSParserCacheHash = hash_create("Tsearch parser cache", 4,
126 &ctl, HASH_ELEM | HASH_FUNCTION);
127 /* Flush cache on pg_ts_parser changes */
128 CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack,
129 PointerGetDatum(TSParserCacheHash));
131 /* Also make sure CacheMemoryContext exists */
132 if (!CacheMemoryContext)
133 CreateCacheMemoryContext();
136 /* Check single-entry cache */
137 if (lastUsedParser && lastUsedParser->prsId == prsId &&
138 lastUsedParser->isvalid)
139 return lastUsedParser;
141 /* Try to look up an existing entry */
142 entry = (TSParserCacheEntry *) hash_search(TSParserCacheHash,
145 if (entry == NULL || !entry->isvalid)
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.
152 Form_pg_ts_parser prs;
154 tp = SearchSysCache(TSPARSEROID,
155 ObjectIdGetDatum(prsId),
157 if (!HeapTupleIsValid(tp))
158 elog(ERROR, "cache lookup failed for text search parser %u",
160 prs = (Form_pg_ts_parser) GETSTRUCT(tp);
165 if (!OidIsValid(prs->prsstart))
166 elog(ERROR, "text search parser %u has no prsstart method", prsId);
167 if (!OidIsValid(prs->prstoken))
168 elog(ERROR, "text search parser %u has no prstoken method", prsId);
169 if (!OidIsValid(prs->prsend))
170 elog(ERROR, "text search parser %u has no prsend method", prsId);
176 /* Now make the cache entry */
177 entry = (TSParserCacheEntry *)
178 hash_search(TSParserCacheHash,
181 Assert(!found); /* it wasn't there a moment ago */
184 MemSet(entry, 0, sizeof(TSParserCacheEntry));
185 entry->prsId = prsId;
186 entry->startOid = prs->prsstart;
187 entry->tokenOid = prs->prstoken;
188 entry->endOid = prs->prsend;
189 entry->headlineOid = prs->prsheadline;
190 entry->lextypeOid = prs->prslextype;
194 fmgr_info_cxt(entry->startOid, &entry->prsstart, CacheMemoryContext);
195 fmgr_info_cxt(entry->tokenOid, &entry->prstoken, CacheMemoryContext);
196 fmgr_info_cxt(entry->endOid, &entry->prsend, CacheMemoryContext);
197 if (OidIsValid(entry->headlineOid))
198 fmgr_info_cxt(entry->headlineOid, &entry->prsheadline,
201 entry->isvalid = true;
204 lastUsedParser = entry;
210 * Fetch dictionary cache entry
212 TSDictionaryCacheEntry *
213 lookup_ts_dictionary_cache(Oid dictId)
215 TSDictionaryCacheEntry *entry;
217 if (TSDictionaryCacheHash == NULL)
219 /* First time through: initialize the hash table */
222 MemSet(&ctl, 0, sizeof(ctl));
223 ctl.keysize = sizeof(Oid);
224 ctl.entrysize = sizeof(TSDictionaryCacheEntry);
226 TSDictionaryCacheHash = hash_create("Tsearch dictionary cache", 8,
227 &ctl, HASH_ELEM | HASH_FUNCTION);
228 /* Flush cache on pg_ts_dict and pg_ts_template changes */
229 CacheRegisterSyscacheCallback(TSDICTOID, InvalidateTSCacheCallBack,
230 PointerGetDatum(TSDictionaryCacheHash));
231 CacheRegisterSyscacheCallback(TSTEMPLATEOID, InvalidateTSCacheCallBack,
232 PointerGetDatum(TSDictionaryCacheHash));
234 /* Also make sure CacheMemoryContext exists */
235 if (!CacheMemoryContext)
236 CreateCacheMemoryContext();
239 /* Check single-entry cache */
240 if (lastUsedDictionary && lastUsedDictionary->dictId == dictId &&
241 lastUsedDictionary->isvalid)
242 return lastUsedDictionary;
244 /* Try to look up an existing entry */
245 entry = (TSDictionaryCacheEntry *) hash_search(TSDictionaryCacheHash,
248 if (entry == NULL || !entry->isvalid)
251 * If we didn't find one, we want to make one. But first look up the
252 * object to be sure the OID is real.
256 Form_pg_ts_dict dict;
257 Form_pg_ts_template template;
258 MemoryContext saveCtx;
260 tpdict = SearchSysCache(TSDICTOID,
261 ObjectIdGetDatum(dictId),
263 if (!HeapTupleIsValid(tpdict))
264 elog(ERROR, "cache lookup failed for text search dictionary %u",
266 dict = (Form_pg_ts_dict) GETSTRUCT(tpdict);
271 if (!OidIsValid(dict->dicttemplate))
272 elog(ERROR, "text search dictionary %u has no template", dictId);
275 * Retrieve dictionary's template
277 tptmpl = SearchSysCache(TSTEMPLATEOID,
278 ObjectIdGetDatum(dict->dicttemplate),
280 if (!HeapTupleIsValid(tptmpl))
281 elog(ERROR, "cache lookup failed for text search template %u",
283 template = (Form_pg_ts_template) GETSTRUCT(tptmpl);
288 if (!OidIsValid(template->tmpllexize))
289 elog(ERROR, "text search template %u has no lexize method",
290 template->tmpllexize);
296 /* Now make the cache entry */
297 entry = (TSDictionaryCacheEntry *)
298 hash_search(TSDictionaryCacheHash,
301 Assert(!found); /* it wasn't there a moment ago */
303 /* Create private memory context the first time through */
304 saveCtx = AllocSetContextCreate(CacheMemoryContext,
305 NameStr(dict->dictname),
306 ALLOCSET_SMALL_MINSIZE,
307 ALLOCSET_SMALL_INITSIZE,
308 ALLOCSET_SMALL_MAXSIZE);
312 /* Clear the existing entry's private context */
313 saveCtx = entry->dictCtx;
314 MemoryContextResetAndDeleteChildren(saveCtx);
317 MemSet(entry, 0, sizeof(TSDictionaryCacheEntry));
318 entry->dictId = dictId;
319 entry->dictCtx = saveCtx;
321 entry->lexizeOid = template->tmpllexize;
323 if (OidIsValid(template->tmplinit))
328 MemoryContext oldcontext;
331 * Init method runs in dictionary's private memory context, and we
332 * make sure the options are stored there too
334 oldcontext = MemoryContextSwitchTo(entry->dictCtx);
336 opt = SysCacheGetAttr(TSDICTOID, tpdict,
337 Anum_pg_ts_dict_dictinitoption,
342 dictoptions = deserialize_deflist(opt);
345 DatumGetPointer(OidFunctionCall1(template->tmplinit,
346 PointerGetDatum(dictoptions)));
348 MemoryContextSwitchTo(oldcontext);
351 ReleaseSysCache(tptmpl);
352 ReleaseSysCache(tpdict);
354 fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
356 entry->isvalid = true;
359 lastUsedDictionary = entry;
365 * Initialize config cache and prepare callbacks. This is split out of
366 * lookup_ts_config_cache because we need to activate the callback before
367 * caching TSCurrentConfigCache, too.
370 init_ts_config_cache(void)
374 MemSet(&ctl, 0, sizeof(ctl));
375 ctl.keysize = sizeof(Oid);
376 ctl.entrysize = sizeof(TSConfigCacheEntry);
378 TSConfigCacheHash = hash_create("Tsearch configuration cache", 16,
379 &ctl, HASH_ELEM | HASH_FUNCTION);
380 /* Flush cache on pg_ts_config and pg_ts_config_map changes */
381 CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack,
382 PointerGetDatum(TSConfigCacheHash));
383 CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack,
384 PointerGetDatum(TSConfigCacheHash));
386 /* Also make sure CacheMemoryContext exists */
387 if (!CacheMemoryContext)
388 CreateCacheMemoryContext();
392 * Fetch configuration cache entry
395 lookup_ts_config_cache(Oid cfgId)
397 TSConfigCacheEntry *entry;
399 if (TSConfigCacheHash == NULL)
401 /* First time through: initialize the hash table */
402 init_ts_config_cache();
405 /* Check single-entry cache */
406 if (lastUsedConfig && lastUsedConfig->cfgId == cfgId &&
407 lastUsedConfig->isvalid)
408 return lastUsedConfig;
410 /* Try to look up an existing entry */
411 entry = (TSConfigCacheEntry *) hash_search(TSConfigCacheHash,
414 if (entry == NULL || !entry->isvalid)
417 * If we didn't find one, we want to make one. But first look up the
418 * object to be sure the OID is real.
421 Form_pg_ts_config cfg;
427 ListDictionary maplists[MAXTOKENTYPE + 1];
428 Oid mapdicts[MAXDICTSPERTT];
433 tp = SearchSysCache(TSCONFIGOID,
434 ObjectIdGetDatum(cfgId),
436 if (!HeapTupleIsValid(tp))
437 elog(ERROR, "cache lookup failed for text search configuration %u",
439 cfg = (Form_pg_ts_config) GETSTRUCT(tp);
444 if (!OidIsValid(cfg->cfgparser))
445 elog(ERROR, "text search configuration %u has no parser", cfgId);
451 /* Now make the cache entry */
452 entry = (TSConfigCacheEntry *)
453 hash_search(TSConfigCacheHash,
456 Assert(!found); /* it wasn't there a moment ago */
460 /* Cleanup old contents */
463 for (i = 0; i < entry->lenmap; i++)
464 if (entry->map[i].dictIds)
465 pfree(entry->map[i].dictIds);
470 MemSet(entry, 0, sizeof(TSConfigCacheEntry));
471 entry->cfgId = cfgId;
472 entry->prsId = cfg->cfgparser;
477 * Scan pg_ts_config_map to gather dictionary list for each token type
479 * Because the index is on (mapcfg, maptokentype, mapseqno), we will
480 * see the entries in maptokentype order, and in mapseqno order for
481 * each token type, even though we didn't explicitly ask for that.
483 MemSet(maplists, 0, sizeof(maplists));
487 ScanKeyInit(&mapskey,
488 Anum_pg_ts_config_map_mapcfg,
489 BTEqualStrategyNumber, F_OIDEQ,
490 ObjectIdGetDatum(cfgId));
492 maprel = heap_open(TSConfigMapRelationId, AccessShareLock);
493 mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
494 mapscan = systable_beginscan_ordered(maprel, mapidx,
495 SnapshotNow, 1, &mapskey);
497 while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL)
499 Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
500 int toktype = cfgmap->maptokentype;
502 if (toktype <= 0 || toktype > MAXTOKENTYPE)
503 elog(ERROR, "maptokentype value %d is out of range", toktype);
504 if (toktype < maxtokentype)
505 elog(ERROR, "maptokentype entries are out of order");
506 if (toktype > maxtokentype)
508 /* starting a new token type, but first save the prior data */
511 maplists[maxtokentype].len = ndicts;
512 maplists[maxtokentype].dictIds = (Oid *)
513 MemoryContextAlloc(CacheMemoryContext,
514 sizeof(Oid) * ndicts);
515 memcpy(maplists[maxtokentype].dictIds, mapdicts,
516 sizeof(Oid) * ndicts);
518 maxtokentype = toktype;
519 mapdicts[0] = cfgmap->mapdict;
524 /* continuing data for current token type */
525 if (ndicts >= MAXDICTSPERTT)
526 elog(ERROR, "too many pg_ts_config_map entries for one token type");
527 mapdicts[ndicts++] = cfgmap->mapdict;
531 systable_endscan_ordered(mapscan);
532 index_close(mapidx, AccessShareLock);
533 heap_close(maprel, AccessShareLock);
537 /* save the last token type's dictionaries */
538 maplists[maxtokentype].len = ndicts;
539 maplists[maxtokentype].dictIds = (Oid *)
540 MemoryContextAlloc(CacheMemoryContext,
541 sizeof(Oid) * ndicts);
542 memcpy(maplists[maxtokentype].dictIds, mapdicts,
543 sizeof(Oid) * ndicts);
544 /* and save the overall map */
545 entry->lenmap = maxtokentype + 1;
546 entry->map = (ListDictionary *)
547 MemoryContextAlloc(CacheMemoryContext,
548 sizeof(ListDictionary) * entry->lenmap);
549 memcpy(entry->map, maplists,
550 sizeof(ListDictionary) * entry->lenmap);
553 entry->isvalid = true;
556 lastUsedConfig = entry;
562 /*---------------------------------------------------
563 * GUC variable "default_text_search_config"
564 *---------------------------------------------------
568 getTSCurrentConfig(bool emitError)
570 /* if we have a cached value, return it */
571 if (OidIsValid(TSCurrentConfigCache))
572 return TSCurrentConfigCache;
574 /* fail if GUC hasn't been set up yet */
575 if (TSCurrentConfig == NULL || *TSCurrentConfig == '\0')
578 elog(ERROR, "text search configuration isn't set");
583 if (TSConfigCacheHash == NULL)
585 /* First time through: initialize the tsconfig inval callback */
586 init_ts_config_cache();
589 /* Look up the config */
590 TSCurrentConfigCache =
591 TSConfigGetCfgid(stringToQualifiedNameList(TSCurrentConfig),
594 return TSCurrentConfigCache;
598 assignTSCurrentConfig(const char *newval, bool doit, GucSource source)
601 * If we aren't inside a transaction, we cannot do database access so
602 * cannot verify the config name. Must accept it on faith.
604 if (IsTransactionState())
608 Form_pg_ts_config cfg;
611 cfgId = TSConfigGetCfgid(stringToQualifiedNameList(newval), true);
613 if (!OidIsValid(cfgId))
617 * Modify the actually stored value to be fully qualified, to ensure
618 * later changes of search_path don't affect it.
620 tuple = SearchSysCache(TSCONFIGOID,
621 ObjectIdGetDatum(cfgId),
623 if (!HeapTupleIsValid(tuple))
624 elog(ERROR, "cache lookup failed for text search configuration %u",
626 cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
628 buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace),
629 NameStr(cfg->cfgname));
631 ReleaseSysCache(tuple);
633 /* GUC wants it malloc'd not palloc'd */
634 newval = strdup(buf);
638 TSCurrentConfigCache = cfgId;
643 TSCurrentConfigCache = InvalidOid;