*
* ts_cache.c
* Tsearch related object caches.
*
* Tsearch performance is very sensitive to performance of parsers,
* dictionaries and mapping, so lookups should be cached as much
* as possible.
*
* Once a backend has created a cache entry for a particular TS object OID,
* the cache entry will exist for the life of the backend; hence it is
* safe to hold onto a pointer to the cache entry while doing things that
* might result in recognizing a cache invalidation. Beware however that
* subsidiary information might be deleted and reallocated somewhere else
* if a cache inval and reval happens! This does not look like it will be
* a big problem as long as parser and dictionary methods do not attempt
* any database access.
*
*
* Copyright (c) 2006-2012, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/utils/cache/ts_cache.c
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/reloptions.h"
#include "access/xact.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_config_map.h"
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_ts_parser.h"
#include "catalog/pg_ts_template.h"
#include "commands/defrem.h"
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
#include "utils/snapmgr.h"
* MAXTOKENTYPE/MAXDICTSPERTT are arbitrary limits on the workspace size
* used in lookup_ts_config_cache(). We could avoid hardwiring a limit
* by making the workspace dynamically enlargeable, but it seems unlikely
* to be worth the trouble.
*/
#define MAXTOKENTYPE 256
#define MAXDICTSPERTT 100
* We use this syscache callback to detect when a visible change to a TS
* catalog entry has been made, by either our own backend or another one.
*
* In principle we could just flush the specific cache entry that changed,
* but given that TS configuration changes are probably infrequent, it
* doesn't seem worth the trouble to determine that; we just flush all the
* entries of the related hash table.
*
* We can use the same function for all TS caches by passing the hash
* table address as the "arg".
*/
static void InvalidateTSCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
{
HTAB* hash = (HTAB*)DatumGetPointer(arg);
HASH_SEQ_STATUS status;
TSAnyCacheEntry* entry = NULL;
hash_seq_init(&status, hash);
while ((entry = (TSAnyCacheEntry*)hash_seq_search(&status)) != NULL)
entry->isvalid = false;
if (hash == u_sess->tscache_cxt.TSConfigCacheHash)
u_sess->tscache_cxt.TSCurrentConfigCache = InvalidOid;
}
void init_ts_parser_cache()
{
HASHCTL ctl;
errno_t rc;
rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl));
securec_check(rc, "\0", "\0");
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(TSParserCacheEntry);
ctl.hash = oid_hash;
ctl.hcxt = u_sess->cache_mem_cxt;
u_sess->tscache_cxt.TSParserCacheHash =
hash_create("Tsearch parser cache", 4, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
CacheRegisterSessionSyscacheCallback(
TSPARSEROID, InvalidateTSCacheCallBack, PointerGetDatum(u_sess->tscache_cxt.TSParserCacheHash));
}
* Fetch parser cache entry
*/
TSParserCacheEntry* lookup_ts_parser_cache(Oid prsId)
{
TSParserCacheEntry* entry = NULL;
errno_t rc;
if (u_sess->tscache_cxt.TSParserCacheHash == NULL) {
init_ts_parser_cache();
}
if (u_sess->tscache_cxt.lastUsedParser && u_sess->tscache_cxt.lastUsedParser->prsId == prsId &&
u_sess->tscache_cxt.lastUsedParser->isvalid)
return u_sess->tscache_cxt.lastUsedParser;
entry = (TSParserCacheEntry*)hash_search(u_sess->tscache_cxt.TSParserCacheHash, (void*)&prsId, HASH_FIND, NULL);
if (entry == NULL || !entry->isvalid) {
* If we didn't find one, we want to make one. But first look up the
* object to be sure the OID is real.
*/
HeapTuple tp;
Form_pg_ts_parser prs;
tp = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId));
if (!HeapTupleIsValid(tp)) {
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for text search parser %u", prsId)));
}
prs = (Form_pg_ts_parser)GETSTRUCT(tp);
* Sanity checks
*/
if (!OidIsValid(prs->prsstart))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("text search parser %u has no prsstart method", prsId)));
if (!OidIsValid(prs->prstoken))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("text search parser %u has no prstoken method", prsId)));
if (!OidIsValid(prs->prsend))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("text search parser %u has no prsend method", prsId)));
if (entry == NULL) {
bool found = false;
entry = (TSParserCacheEntry*)hash_search(
u_sess->tscache_cxt.TSParserCacheHash, (void*)&prsId, HASH_ENTER, &found);
Assert(!found);
}
rc = memset_s(entry, sizeof(TSParserCacheEntry), 0, sizeof(TSParserCacheEntry));
securec_check(rc, "\0", "\0");
entry->prsId = prsId;
entry->startOid = prs->prsstart;
entry->tokenOid = prs->prstoken;
entry->endOid = prs->prsend;
entry->headlineOid = prs->prsheadline;
entry->lextypeOid = prs->prslextype;
ReleaseSysCache(tp);
fmgr_info_cxt(entry->startOid, &entry->prsstart, u_sess->cache_mem_cxt);
fmgr_info_cxt(entry->tokenOid, &entry->prstoken, u_sess->cache_mem_cxt);
fmgr_info_cxt(entry->endOid, &entry->prsend, u_sess->cache_mem_cxt);
if (OidIsValid(entry->headlineOid))
fmgr_info_cxt(entry->headlineOid, &entry->prsheadline, u_sess->cache_mem_cxt);
entry->isvalid = true;
}
u_sess->tscache_cxt.lastUsedParser = entry;
return entry;
}
void init_ts_distionary_cache()
{
HASHCTL ctl;
errno_t rc;
rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl));
securec_check(rc, "\0", "\0");
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(TSDictionaryCacheEntry);
ctl.hash = oid_hash;
ctl.hcxt = u_sess->cache_mem_cxt;
u_sess->tscache_cxt.TSDictionaryCacheHash =
hash_create("Tsearch dictionary cache", 8, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
CacheRegisterSessionSyscacheCallback(
TSDICTOID, InvalidateTSCacheCallBack, PointerGetDatum(u_sess->tscache_cxt.TSDictionaryCacheHash));
CacheRegisterSessionSyscacheCallback(
TSTEMPLATEOID, InvalidateTSCacheCallBack, PointerGetDatum(u_sess->tscache_cxt.TSDictionaryCacheHash));
}
* Fetch dictionary cache entry
*/
TSDictionaryCacheEntry* lookup_ts_dictionary_cache(Oid dictId)
{
TSDictionaryCacheEntry* entry = NULL;
errno_t rc;
if (u_sess->tscache_cxt.TSDictionaryCacheHash == NULL) {
init_ts_distionary_cache();
}
if (u_sess->tscache_cxt.lastUsedDictionary && u_sess->tscache_cxt.lastUsedDictionary->dictId == dictId &&
u_sess->tscache_cxt.lastUsedDictionary->isvalid)
return u_sess->tscache_cxt.lastUsedDictionary;
entry = (TSDictionaryCacheEntry*)hash_search(
u_sess->tscache_cxt.TSDictionaryCacheHash, (void*)&dictId, HASH_FIND, NULL);
if (entry == NULL || !entry->isvalid) {
* If we didn't find one, we want to make one. But first look up the
* object to be sure the OID is real.
*/
HeapTuple tpdict, tptmpl;
Form_pg_ts_dict dict;
Form_pg_ts_template templ;
MemoryContext saveCtx;
tpdict = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
if (!HeapTupleIsValid(tpdict)) {
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for text search dictionary %u", dictId)));
}
dict = (Form_pg_ts_dict)GETSTRUCT(tpdict);
* Sanity checks
*/
if (!OidIsValid(dict->dicttemplate)) {
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("text search dictionary %u has no template", dictId)));
}
* Retrieve dictionary's template
*/
tptmpl = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(dict->dicttemplate));
if (!HeapTupleIsValid(tptmpl)) {
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for text search template %u", dict->dicttemplate)));
}
templ = (Form_pg_ts_template)GETSTRUCT(tptmpl);
* Sanity checks
*/
if (!OidIsValid(templ->tmpllexize)) {
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
errmsg("text search template %u has no lexize method", templ->tmpllexize)));
}
if (entry == NULL) {
bool found = false;
entry = (TSDictionaryCacheEntry*)hash_search(
u_sess->tscache_cxt.TSDictionaryCacheHash, (void*)&dictId, HASH_ENTER, &found);
Assert(!found);
saveCtx = AllocSetContextCreate(u_sess->cache_mem_cxt,
NameStr(dict->dictname),
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
} else {
saveCtx = entry->dictCtx;
MemoryContextResetAndDeleteChildren(saveCtx);
}
rc = memset_s(entry, sizeof(TSDictionaryCacheEntry), 0, sizeof(TSDictionaryCacheEntry));
securec_check(rc, "\0", "\0");
entry->dictId = dictId;
entry->dictCtx = saveCtx;
entry->lexizeOid = templ->tmpllexize;
if (OidIsValid(templ->tmplinit)) {
List* dictoptions = NIL;
Datum opt;
bool isnull = false;
MemoryContext oldcontext;
* Init method runs in dictionary's private memory context, and we
* make sure the options are stored there too
*/
oldcontext = MemoryContextSwitchTo(entry->dictCtx);
opt = SysCacheGetAttr(TSDICTOID, tpdict, Anum_pg_ts_dict_dictinitoption, &isnull);
if (isnull) {
dictoptions = NIL;
} else {
dictoptions = deserialize_deflist(opt);
}
entry->dictData = DatumGetPointer(OidFunctionCall1(templ->tmplinit, PointerGetDatum(dictoptions)));
(void)MemoryContextSwitchTo(oldcontext);
}
ReleaseSysCache(tptmpl);
ReleaseSysCache(tpdict);
fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
entry->isvalid = true;
}
u_sess->tscache_cxt.lastUsedDictionary = entry;
return entry;
}
* Initialize config cache and prepare callbacks. This is split out of
* lookup_ts_config_cache because we need to activate the callback before
* caching u_sess->tscache_cxt.TSCurrentConfigCache, too.
*/
static void init_ts_config_cache(void)
{
HASHCTL ctl;
errno_t rc;
rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl));
securec_check(rc, "\0", "\0");
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(TSConfigCacheEntry);
ctl.hash = oid_hash;
ctl.hcxt = u_sess->cache_mem_cxt;
u_sess->tscache_cxt.TSConfigCacheHash =
hash_create("Tsearch configuration cache", 16, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
CacheRegisterSessionSyscacheCallback(
TSCONFIGOID, InvalidateTSCacheCallBack, PointerGetDatum(u_sess->tscache_cxt.TSConfigCacheHash));
CacheRegisterSessionSyscacheCallback(
TSCONFIGMAP, InvalidateTSCacheCallBack, PointerGetDatum(u_sess->tscache_cxt.TSConfigCacheHash));
}
* Fetch configuration cache entry
*/
TSConfigCacheEntry* lookup_ts_config_cache(Oid cfgId)
{
TSConfigCacheEntry* entry = NULL;
if (u_sess->tscache_cxt.TSConfigCacheHash == NULL) {
init_ts_config_cache();
}
if (u_sess->tscache_cxt.lastUsedConfig && u_sess->tscache_cxt.lastUsedConfig->cfgId == cfgId &&
u_sess->tscache_cxt.lastUsedConfig->isvalid) {
return u_sess->tscache_cxt.lastUsedConfig;
}
entry = (TSConfigCacheEntry*)hash_search(u_sess->tscache_cxt.TSConfigCacheHash, (void*)&cfgId, HASH_FIND, NULL);
if (entry == NULL || !entry->isvalid) {
* If we didn't find one, we want to make one. But first look up the
* object to be sure the OID is real.
*/
HeapTuple tp;
Form_pg_ts_config cfg;
Relation maprel;
Relation mapidx;
ScanKeyData mapskey;
SysScanDesc mapscan;
HeapTuple maptup;
ListDictionary maplists[MAXTOKENTYPE + 1];
Oid mapdicts[MAXDICTSPERTT];
int maxtokentype;
int ndicts;
Datum optdatum;
bool isnull = false;
int i;
errno_t rc;
tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
if (!HeapTupleIsValid(tp)) {
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for text search configuration %u", cfgId)));
}
cfg = (Form_pg_ts_config)GETSTRUCT(tp);
* Sanity checks
*/
if (!OidIsValid(cfg->cfgparser)) {
ereport(ERROR,
(errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("text search configuration %u has no parser", cfgId)));
}
if (entry == NULL) {
bool found = false;
entry = (TSConfigCacheEntry*)hash_search(
u_sess->tscache_cxt.TSConfigCacheHash, (void*)&cfgId, HASH_ENTER, &found);
Assert(!found);
} else {
if (entry->map) {
for (i = 0; i < entry->lenmap; i++)
if (entry->map[i].dictIds)
pfree_ext(entry->map[i].dictIds);
pfree_ext(entry->map);
}
}
rc = memset_s(entry, sizeof(TSConfigCacheEntry), 0, sizeof(TSConfigCacheEntry));
securec_check(rc, "", "");
entry->cfgId = cfgId;
entry->prsId = cfg->cfgparser;
entry->opts = NULL;
optdatum = SysCacheGetAttr(TSCONFIGOID, tp, Anum_pg_ts_config_cfoptions, &isnull);
bytea* bytea_opts = tsearch_config_reloptions(optdatum, false, cfg->cfgparser, true);
if (PointerIsValid(bytea_opts)) {
Assert(
cfg->cfgparser == NGRAM_PARSER || cfg->cfgparser == ZHPARSER_PARSER || cfg->cfgparser == POUND_PARSER);
entry->opts = (ParserCfOpts*)MemoryContextAlloc(u_sess->cache_mem_cxt, VARSIZE(bytea_opts));
rc = memcpy_s(entry->opts, VARSIZE(bytea_opts), bytea_opts, VARSIZE(bytea_opts));
securec_check(rc, "\0", "\0");
}
ReleaseSysCache(tp);
* Scan pg_ts_config_map to gather dictionary list for each token type
*
* Because the index is on (mapcfg, maptokentype, mapseqno), we will
* see the entries in maptokentype order, and in mapseqno order for
* each token type, even though we didn't explicitly ask for that.
*/
rc = memset_s(maplists, sizeof(maplists), 0, sizeof(maplists));
securec_check(rc, "\0", "\0");
maxtokentype = 0;
ndicts = 0;
ScanKeyInit(&mapskey, Anum_pg_ts_config_map_mapcfg, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(cfgId));
maprel = heap_open(TSConfigMapRelationId, AccessShareLock);
mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
mapscan = systable_beginscan_ordered(maprel, mapidx, SnapshotNow, 1, &mapskey);
while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL) {
Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map)GETSTRUCT(maptup);
int toktype = cfgmap->maptokentype;
if (toktype <= 0 || toktype > MAXTOKENTYPE)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("maptokentype value %d is out of range", toktype)));
if (toktype < maxtokentype)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("too many pg_ts_config_map entries for one token type")));
if (toktype > maxtokentype) {
if (ndicts > 0) {
maplists[maxtokentype].len = ndicts;
maplists[maxtokentype].dictIds =
(Oid*)MemoryContextAlloc(u_sess->cache_mem_cxt, sizeof(Oid) * ndicts);
rc = memcpy_s(maplists[maxtokentype].dictIds, sizeof(Oid) * ndicts, mapdicts, sizeof(Oid) * ndicts);
securec_check(rc, "\0", "\0");
}
maxtokentype = toktype;
mapdicts[0] = cfgmap->mapdict;
ndicts = 1;
} else {
if (ndicts >= MAXDICTSPERTT)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ROWS),
errmsg("too many pg_ts_config_map entries for one token type")));
mapdicts[ndicts++] = cfgmap->mapdict;
}
}
systable_endscan_ordered(mapscan);
index_close(mapidx, AccessShareLock);
heap_close(maprel, AccessShareLock);
if (ndicts > 0) {
maplists[maxtokentype].len = ndicts;
maplists[maxtokentype].dictIds = (Oid*)MemoryContextAlloc(u_sess->cache_mem_cxt, sizeof(Oid) * ndicts);
rc = memcpy_s(maplists[maxtokentype].dictIds, sizeof(Oid) * ndicts, mapdicts, sizeof(Oid) * ndicts);
securec_check(rc, "", "");
entry->lenmap = maxtokentype + 1;
entry->map =
(ListDictionary*)MemoryContextAlloc(u_sess->cache_mem_cxt, sizeof(ListDictionary) * entry->lenmap);
rc = memcpy_s(
entry->map, sizeof(ListDictionary) * entry->lenmap, maplists, sizeof(ListDictionary) * entry->lenmap);
securec_check(rc, "", "");
}
entry->isvalid = true;
}
u_sess->tscache_cxt.lastUsedConfig = entry;
return entry;
}
* GUC variable "default_text_search_config"
* ---------------------------------------------------
*/
Oid getTSCurrentConfig(bool emitError)
{
if (OidIsValid(u_sess->tscache_cxt.TSCurrentConfigCache))
return u_sess->tscache_cxt.TSCurrentConfigCache;
if (u_sess->attr.attr_common.TSCurrentConfig == NULL || *u_sess->attr.attr_common.TSCurrentConfig == '\0') {
if (emitError)
ereport(ERROR, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("text search configuration isn't set")));
else
return InvalidOid;
}
if (u_sess->tscache_cxt.TSConfigCacheHash == NULL) {
init_ts_config_cache();
}
u_sess->tscache_cxt.TSCurrentConfigCache =
get_ts_config_oid(stringToQualifiedNameList(u_sess->attr.attr_common.TSCurrentConfig), !emitError);
return u_sess->tscache_cxt.TSCurrentConfigCache;
}
bool check_TSCurrentConfig(char** newval, void** extra, GucSource source)
{
* If we aren't inside a transaction, we cannot do database access so
* cannot verify the config name. Must accept it on faith.
*/
if (IsTransactionState()) {
Oid cfg_id;
HeapTuple tuple;
Form_pg_ts_config cfg;
char* buf = NULL;
cfg_id = get_ts_config_oid(stringToQualifiedNameList(*newval), true);
* When source == PGC_S_TEST, we are checking the argument of an ALTER
* DATABASE SET or ALTER USER SET command. It could be that the
* intended use of the setting is for some other database, so we
* should not error out if the text search configuration is not
* present in the current database. We issue a NOTICE instead.
*/
if (!OidIsValid(cfg_id)) {
if (source == PGC_S_TEST) {
ereport(NOTICE,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("text search configuration \"%s\" does not exist", *newval)));
return true;
} else
return false;
}
* Modify the actually stored value to be fully qualified, to ensure
* later changes of search_path don't affect it.
*/
tuple = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfg_id));
if (!HeapTupleIsValid(tuple)) {
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for text search configuration %u", cfg_id)));
}
cfg = (Form_pg_ts_config)GETSTRUCT(tuple);
buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace), NameStr(cfg->cfgname));
ReleaseSysCache(tuple);
pfree(*newval);
*newval = MemoryContextStrdup(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE), buf);
pfree(buf);
buf = NULL;
if (!*newval) {
return false;
}
}
return true;
}
void assign_TSCurrentConfig(const char* newval, void* extra)
{
u_sess->tscache_cxt.TSCurrentConfigCache = InvalidOid;
}