Revert dubious message wording change.
[PostgreSQL.git] / src / backend / utils / cache / ts_cache.c
blobd930eb23a43fed8456c8e87f78ce2f7e4a4824d4
1 /*-------------------------------------------------------------------------
3 * ts_cache.c
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
8 * as possible.
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
22 * IDENTIFICATION
23 * $PostgreSQL$
25 *-------------------------------------------------------------------------
27 #include "postgres.h"
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/catcache.h"
46 #include "utils/fmgroids.h"
47 #include "utils/inval.h"
48 #include "utils/lsyscache.h"
49 #include "utils/memutils.h"
50 #include "utils/syscache.h"
51 #include "utils/tqual.h"
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.
60 #define MAXTOKENTYPE 256
61 #define MAXDICTSPERTT 100
64 static HTAB *TSParserCacheHash = NULL;
65 static TSParserCacheEntry *lastUsedParser = NULL;
67 static HTAB *TSDictionaryCacheHash = NULL;
68 static TSDictionaryCacheEntry *lastUsedDictionary = NULL;
70 static HTAB *TSConfigCacheHash = NULL;
71 static TSConfigCacheEntry *lastUsedConfig = NULL;
74 * GUC default_text_search_config, and a cache of the current config's OID
76 char *TSCurrentConfig = NULL;
78 static Oid TSCurrentConfigCache = InvalidOid;
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.
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.
90 * We can use the same function for all TS caches by passing the hash
91 * table address as the "arg".
93 static void
94 InvalidateTSCacheCallBack(Datum arg, int cacheid, ItemPointer tuplePtr)
96 HTAB *hash = (HTAB *) DatumGetPointer(arg);
97 HASH_SEQ_STATUS status;
98 TSAnyCacheEntry *entry;
100 hash_seq_init(&status, hash);
101 while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL)
102 entry->isvalid = false;
104 /* Also invalidate the current-config cache if it's pg_ts_config */
105 if (hash == TSConfigCacheHash)
106 TSCurrentConfigCache = InvalidOid;
110 * Fetch parser cache entry
112 TSParserCacheEntry *
113 lookup_ts_parser_cache(Oid prsId)
115 TSParserCacheEntry *entry;
117 if (TSParserCacheHash == NULL)
119 /* First time through: initialize the hash table */
120 HASHCTL ctl;
122 if (!CacheMemoryContext)
123 CreateCacheMemoryContext();
125 MemSet(&ctl, 0, sizeof(ctl));
126 ctl.keysize = sizeof(Oid);
127 ctl.entrysize = sizeof(TSParserCacheEntry);
128 ctl.hash = oid_hash;
129 TSParserCacheHash = hash_create("Tsearch parser cache", 4,
130 &ctl, HASH_ELEM | HASH_FUNCTION);
131 /* Flush cache on pg_ts_parser changes */
132 CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack,
133 PointerGetDatum(TSParserCacheHash));
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,
143 (void *) &prsId,
144 HASH_FIND, NULL);
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.
151 HeapTuple tp;
152 Form_pg_ts_parser prs;
154 tp = SearchSysCache(TSPARSEROID,
155 ObjectIdGetDatum(prsId),
156 0, 0, 0);
157 if (!HeapTupleIsValid(tp))
158 elog(ERROR, "cache lookup failed for text search parser %u",
159 prsId);
160 prs = (Form_pg_ts_parser) GETSTRUCT(tp);
163 * Sanity checks
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);
172 if (entry == NULL)
174 bool found;
176 /* Now make the cache entry */
177 entry = (TSParserCacheEntry *)
178 hash_search(TSParserCacheHash,
179 (void *) &prsId,
180 HASH_ENTER, &found);
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;
192 ReleaseSysCache(tp);
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,
199 CacheMemoryContext);
201 entry->isvalid = true;
204 lastUsedParser = entry;
206 return 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 */
220 HASHCTL ctl;
222 if (!CacheMemoryContext)
223 CreateCacheMemoryContext();
225 MemSet(&ctl, 0, sizeof(ctl));
226 ctl.keysize = sizeof(Oid);
227 ctl.entrysize = sizeof(TSDictionaryCacheEntry);
228 ctl.hash = oid_hash;
229 TSDictionaryCacheHash = hash_create("Tsearch dictionary cache", 8,
230 &ctl, HASH_ELEM | HASH_FUNCTION);
231 /* Flush cache on pg_ts_dict and pg_ts_template changes */
232 CacheRegisterSyscacheCallback(TSDICTOID, InvalidateTSCacheCallBack,
233 PointerGetDatum(TSDictionaryCacheHash));
234 CacheRegisterSyscacheCallback(TSTEMPLATEOID, InvalidateTSCacheCallBack,
235 PointerGetDatum(TSDictionaryCacheHash));
238 /* Check single-entry cache */
239 if (lastUsedDictionary && lastUsedDictionary->dictId == dictId &&
240 lastUsedDictionary->isvalid)
241 return lastUsedDictionary;
243 /* Try to look up an existing entry */
244 entry = (TSDictionaryCacheEntry *) hash_search(TSDictionaryCacheHash,
245 (void *) &dictId,
246 HASH_FIND, NULL);
247 if (entry == NULL || !entry->isvalid)
250 * If we didn't find one, we want to make one. But first look up the
251 * object to be sure the OID is real.
253 HeapTuple tpdict,
254 tptmpl;
255 Form_pg_ts_dict dict;
256 Form_pg_ts_template template;
257 MemoryContext saveCtx;
259 tpdict = SearchSysCache(TSDICTOID,
260 ObjectIdGetDatum(dictId),
261 0, 0, 0);
262 if (!HeapTupleIsValid(tpdict))
263 elog(ERROR, "cache lookup failed for text search dictionary %u",
264 dictId);
265 dict = (Form_pg_ts_dict) GETSTRUCT(tpdict);
268 * Sanity checks
270 if (!OidIsValid(dict->dicttemplate))
271 elog(ERROR, "text search dictionary %u has no template", dictId);
274 * Retrieve dictionary's template
276 tptmpl = SearchSysCache(TSTEMPLATEOID,
277 ObjectIdGetDatum(dict->dicttemplate),
278 0, 0, 0);
279 if (!HeapTupleIsValid(tptmpl))
280 elog(ERROR, "cache lookup failed for text search template %u",
281 dict->dicttemplate);
282 template = (Form_pg_ts_template) GETSTRUCT(tptmpl);
285 * Sanity checks
287 if (!OidIsValid(template->tmpllexize))
288 elog(ERROR, "text search template %u has no lexize method",
289 template->tmpllexize);
291 if (entry == NULL)
293 bool found;
295 /* Now make the cache entry */
296 entry = (TSDictionaryCacheEntry *)
297 hash_search(TSDictionaryCacheHash,
298 (void *) &dictId,
299 HASH_ENTER, &found);
300 Assert(!found); /* it wasn't there a moment ago */
302 /* Create private memory context the first time through */
303 saveCtx = AllocSetContextCreate(CacheMemoryContext,
304 NameStr(dict->dictname),
305 ALLOCSET_SMALL_MINSIZE,
306 ALLOCSET_SMALL_INITSIZE,
307 ALLOCSET_SMALL_MAXSIZE);
309 else
311 /* Clear the existing entry's private context */
312 saveCtx = entry->dictCtx;
313 MemoryContextResetAndDeleteChildren(saveCtx);
316 MemSet(entry, 0, sizeof(TSDictionaryCacheEntry));
317 entry->dictId = dictId;
318 entry->dictCtx = saveCtx;
320 entry->lexizeOid = template->tmpllexize;
322 if (OidIsValid(template->tmplinit))
324 List *dictoptions;
325 Datum opt;
326 bool isnull;
327 MemoryContext oldcontext;
330 * Init method runs in dictionary's private memory context, and we
331 * make sure the options are stored there too
333 oldcontext = MemoryContextSwitchTo(entry->dictCtx);
335 opt = SysCacheGetAttr(TSDICTOID, tpdict,
336 Anum_pg_ts_dict_dictinitoption,
337 &isnull);
338 if (isnull)
339 dictoptions = NIL;
340 else
341 dictoptions = deserialize_deflist(opt);
343 entry->dictData =
344 DatumGetPointer(OidFunctionCall1(template->tmplinit,
345 PointerGetDatum(dictoptions)));
347 MemoryContextSwitchTo(oldcontext);
350 ReleaseSysCache(tptmpl);
351 ReleaseSysCache(tpdict);
353 fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
355 entry->isvalid = true;
358 lastUsedDictionary = entry;
360 return entry;
364 * Initialize config cache and prepare callbacks. This is split out of
365 * lookup_ts_config_cache because we need to activate the callback before
366 * caching TSCurrentConfigCache, too.
368 static void
369 init_ts_config_cache(void)
371 HASHCTL ctl;
373 if (!CacheMemoryContext)
374 CreateCacheMemoryContext();
376 MemSet(&ctl, 0, sizeof(ctl));
377 ctl.keysize = sizeof(Oid);
378 ctl.entrysize = sizeof(TSConfigCacheEntry);
379 ctl.hash = oid_hash;
380 TSConfigCacheHash = hash_create("Tsearch configuration cache", 16,
381 &ctl, HASH_ELEM | HASH_FUNCTION);
382 /* Flush cache on pg_ts_config and pg_ts_config_map changes */
383 CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack,
384 PointerGetDatum(TSConfigCacheHash));
385 CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack,
386 PointerGetDatum(TSConfigCacheHash));
390 * Fetch configuration cache entry
392 TSConfigCacheEntry *
393 lookup_ts_config_cache(Oid cfgId)
395 TSConfigCacheEntry *entry;
397 if (TSConfigCacheHash == NULL)
399 /* First time through: initialize the hash table */
400 init_ts_config_cache();
403 /* Check single-entry cache */
404 if (lastUsedConfig && lastUsedConfig->cfgId == cfgId &&
405 lastUsedConfig->isvalid)
406 return lastUsedConfig;
408 /* Try to look up an existing entry */
409 entry = (TSConfigCacheEntry *) hash_search(TSConfigCacheHash,
410 (void *) &cfgId,
411 HASH_FIND, NULL);
412 if (entry == NULL || !entry->isvalid)
415 * If we didn't find one, we want to make one. But first look up the
416 * object to be sure the OID is real.
418 HeapTuple tp;
419 Form_pg_ts_config cfg;
420 Relation maprel;
421 Relation mapidx;
422 ScanKeyData mapskey;
423 SysScanDesc mapscan;
424 HeapTuple maptup;
425 ListDictionary maplists[MAXTOKENTYPE + 1];
426 Oid mapdicts[MAXDICTSPERTT];
427 int maxtokentype;
428 int ndicts;
429 int i;
431 tp = SearchSysCache(TSCONFIGOID,
432 ObjectIdGetDatum(cfgId),
433 0, 0, 0);
434 if (!HeapTupleIsValid(tp))
435 elog(ERROR, "cache lookup failed for text search configuration %u",
436 cfgId);
437 cfg = (Form_pg_ts_config) GETSTRUCT(tp);
440 * Sanity checks
442 if (!OidIsValid(cfg->cfgparser))
443 elog(ERROR, "text search configuration %u has no parser", cfgId);
445 if (entry == NULL)
447 bool found;
449 /* Now make the cache entry */
450 entry = (TSConfigCacheEntry *)
451 hash_search(TSConfigCacheHash,
452 (void *) &cfgId,
453 HASH_ENTER, &found);
454 Assert(!found); /* it wasn't there a moment ago */
456 else
458 /* Cleanup old contents */
459 if (entry->map)
461 for (i = 0; i < entry->lenmap; i++)
462 if (entry->map[i].dictIds)
463 pfree(entry->map[i].dictIds);
464 pfree(entry->map);
468 MemSet(entry, 0, sizeof(TSConfigCacheEntry));
469 entry->cfgId = cfgId;
470 entry->prsId = cfg->cfgparser;
472 ReleaseSysCache(tp);
475 * Scan pg_ts_config_map to gather dictionary list for each token type
477 * Because the index is on (mapcfg, maptokentype, mapseqno), we will
478 * see the entries in maptokentype order, and in mapseqno order for
479 * each token type, even though we didn't explicitly ask for that.
481 MemSet(maplists, 0, sizeof(maplists));
482 maxtokentype = 0;
483 ndicts = 0;
485 ScanKeyInit(&mapskey,
486 Anum_pg_ts_config_map_mapcfg,
487 BTEqualStrategyNumber, F_OIDEQ,
488 ObjectIdGetDatum(cfgId));
490 maprel = heap_open(TSConfigMapRelationId, AccessShareLock);
491 mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
492 mapscan = systable_beginscan_ordered(maprel, mapidx,
493 SnapshotNow, 1, &mapskey);
495 while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL)
497 Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
498 int toktype = cfgmap->maptokentype;
500 if (toktype <= 0 || toktype > MAXTOKENTYPE)
501 elog(ERROR, "maptokentype value %d is out of range", toktype);
502 if (toktype < maxtokentype)
503 elog(ERROR, "maptokentype entries are out of order");
504 if (toktype > maxtokentype)
506 /* starting a new token type, but first save the prior data */
507 if (ndicts > 0)
509 maplists[maxtokentype].len = ndicts;
510 maplists[maxtokentype].dictIds = (Oid *)
511 MemoryContextAlloc(CacheMemoryContext,
512 sizeof(Oid) * ndicts);
513 memcpy(maplists[maxtokentype].dictIds, mapdicts,
514 sizeof(Oid) * ndicts);
516 maxtokentype = toktype;
517 mapdicts[0] = cfgmap->mapdict;
518 ndicts = 1;
520 else
522 /* continuing data for current token type */
523 if (ndicts >= MAXDICTSPERTT)
524 elog(ERROR, "too many pg_ts_config_map entries for one token type");
525 mapdicts[ndicts++] = cfgmap->mapdict;
529 systable_endscan_ordered(mapscan);
530 index_close(mapidx, AccessShareLock);
531 heap_close(maprel, AccessShareLock);
533 if (ndicts > 0)
535 /* save the last token type's dictionaries */
536 maplists[maxtokentype].len = ndicts;
537 maplists[maxtokentype].dictIds = (Oid *)
538 MemoryContextAlloc(CacheMemoryContext,
539 sizeof(Oid) * ndicts);
540 memcpy(maplists[maxtokentype].dictIds, mapdicts,
541 sizeof(Oid) * ndicts);
542 /* and save the overall map */
543 entry->lenmap = maxtokentype + 1;
544 entry->map = (ListDictionary *)
545 MemoryContextAlloc(CacheMemoryContext,
546 sizeof(ListDictionary) * entry->lenmap);
547 memcpy(entry->map, maplists,
548 sizeof(ListDictionary) * entry->lenmap);
551 entry->isvalid = true;
554 lastUsedConfig = entry;
556 return entry;
560 /*---------------------------------------------------
561 * GUC variable "default_text_search_config"
562 *---------------------------------------------------
566 getTSCurrentConfig(bool emitError)
568 /* if we have a cached value, return it */
569 if (OidIsValid(TSCurrentConfigCache))
570 return TSCurrentConfigCache;
572 /* fail if GUC hasn't been set up yet */
573 if (TSCurrentConfig == NULL || *TSCurrentConfig == '\0')
575 if (emitError)
576 elog(ERROR, "text search configuration isn't set");
577 else
578 return InvalidOid;
581 if (TSConfigCacheHash == NULL)
583 /* First time through: initialize the tsconfig inval callback */
584 init_ts_config_cache();
587 /* Look up the config */
588 TSCurrentConfigCache =
589 TSConfigGetCfgid(stringToQualifiedNameList(TSCurrentConfig),
590 !emitError);
592 return TSCurrentConfigCache;
595 const char *
596 assignTSCurrentConfig(const char *newval, bool doit, GucSource source)
599 * If we aren't inside a transaction, we cannot do database access so
600 * cannot verify the config name. Must accept it on faith.
602 if (IsTransactionState())
604 Oid cfgId;
605 HeapTuple tuple;
606 Form_pg_ts_config cfg;
607 char *buf;
609 cfgId = TSConfigGetCfgid(stringToQualifiedNameList(newval), true);
611 if (!OidIsValid(cfgId))
612 return NULL;
615 * Modify the actually stored value to be fully qualified, to ensure
616 * later changes of search_path don't affect it.
618 tuple = SearchSysCache(TSCONFIGOID,
619 ObjectIdGetDatum(cfgId),
620 0, 0, 0);
621 if (!HeapTupleIsValid(tuple))
622 elog(ERROR, "cache lookup failed for text search configuration %u",
623 cfgId);
624 cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
626 buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace),
627 NameStr(cfg->cfgname));
629 ReleaseSysCache(tuple);
631 /* GUC wants it malloc'd not palloc'd */
632 newval = strdup(buf);
633 pfree(buf);
635 if (doit && newval)
636 TSCurrentConfigCache = cfgId;
638 else
640 if (doit)
641 TSCurrentConfigCache = InvalidOid;
644 return newval;