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
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/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".
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
113 lookup_ts_parser_cache(Oid prsId
)
115 TSParserCacheEntry
*entry
;
117 if (TSParserCacheHash
== NULL
)
119 /* First time through: initialize the hash table */
122 if (!CacheMemoryContext
)
123 CreateCacheMemoryContext();
125 MemSet(&ctl
, 0, sizeof(ctl
));
126 ctl
.keysize
= sizeof(Oid
);
127 ctl
.entrysize
= sizeof(TSParserCacheEntry
);
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
,
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 if (!CacheMemoryContext
)
223 CreateCacheMemoryContext();
225 MemSet(&ctl
, 0, sizeof(ctl
));
226 ctl
.keysize
= sizeof(Oid
);
227 ctl
.entrysize
= sizeof(TSDictionaryCacheEntry
);
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
,
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.
255 Form_pg_ts_dict dict
;
256 Form_pg_ts_template
template;
257 MemoryContext saveCtx
;
259 tpdict
= SearchSysCache(TSDICTOID
,
260 ObjectIdGetDatum(dictId
),
262 if (!HeapTupleIsValid(tpdict
))
263 elog(ERROR
, "cache lookup failed for text search dictionary %u",
265 dict
= (Form_pg_ts_dict
) GETSTRUCT(tpdict
);
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
),
279 if (!HeapTupleIsValid(tptmpl
))
280 elog(ERROR
, "cache lookup failed for text search template %u",
282 template = (Form_pg_ts_template
) GETSTRUCT(tptmpl
);
287 if (!OidIsValid(template->tmpllexize
))
288 elog(ERROR
, "text search template %u has no lexize method",
289 template->tmpllexize
);
295 /* Now make the cache entry */
296 entry
= (TSDictionaryCacheEntry
*)
297 hash_search(TSDictionaryCacheHash
,
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
);
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
))
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
,
341 dictoptions
= deserialize_deflist(opt
);
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
;
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.
369 init_ts_config_cache(void)
373 if (!CacheMemoryContext
)
374 CreateCacheMemoryContext();
376 MemSet(&ctl
, 0, sizeof(ctl
));
377 ctl
.keysize
= sizeof(Oid
);
378 ctl
.entrysize
= sizeof(TSConfigCacheEntry
);
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
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
,
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.
419 Form_pg_ts_config cfg
;
425 ListDictionary maplists
[MAXTOKENTYPE
+ 1];
426 Oid mapdicts
[MAXDICTSPERTT
];
431 tp
= SearchSysCache(TSCONFIGOID
,
432 ObjectIdGetDatum(cfgId
),
434 if (!HeapTupleIsValid(tp
))
435 elog(ERROR
, "cache lookup failed for text search configuration %u",
437 cfg
= (Form_pg_ts_config
) GETSTRUCT(tp
);
442 if (!OidIsValid(cfg
->cfgparser
))
443 elog(ERROR
, "text search configuration %u has no parser", cfgId
);
449 /* Now make the cache entry */
450 entry
= (TSConfigCacheEntry
*)
451 hash_search(TSConfigCacheHash
,
454 Assert(!found
); /* it wasn't there a moment ago */
458 /* Cleanup old contents */
461 for (i
= 0; i
< entry
->lenmap
; i
++)
462 if (entry
->map
[i
].dictIds
)
463 pfree(entry
->map
[i
].dictIds
);
468 MemSet(entry
, 0, sizeof(TSConfigCacheEntry
));
469 entry
->cfgId
= cfgId
;
470 entry
->prsId
= cfg
->cfgparser
;
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
));
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 */
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
;
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
);
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
;
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')
576 elog(ERROR
, "text search configuration isn't set");
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
),
592 return TSCurrentConfigCache
;
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())
606 Form_pg_ts_config cfg
;
609 cfgId
= TSConfigGetCfgid(stringToQualifiedNameList(newval
), true);
611 if (!OidIsValid(cfgId
))
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
),
621 if (!HeapTupleIsValid(tuple
))
622 elog(ERROR
, "cache lookup failed for text search configuration %u",
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
);
636 TSCurrentConfigCache
= cfgId
;
641 TSCurrentConfigCache
= InvalidOid
;