summaryrefslogtreecommitdiffstats
path: root/src/backend/utils/cache/ts_cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/cache/ts_cache.c')
-rw-r--r--src/backend/utils/cache/ts_cache.c652
1 files changed, 652 insertions, 0 deletions
diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c
new file mode 100644
index 0000000..24808df
--- /dev/null
+++ b/src/backend/utils/cache/ts_cache.c
@@ -0,0 +1,652 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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-2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/cache/ts_cache.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.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 "miscadmin.h"
+#include "tsearch/ts_cache.h"
+#include "utils/builtins.h"
+#include "utils/catcache.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/regproc.h"
+#include "utils/syscache.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
+
+
+static HTAB *TSParserCacheHash = NULL;
+static TSParserCacheEntry *lastUsedParser = NULL;
+
+static HTAB *TSDictionaryCacheHash = NULL;
+static TSDictionaryCacheEntry *lastUsedDictionary = NULL;
+
+static HTAB *TSConfigCacheHash = NULL;
+static TSConfigCacheEntry *lastUsedConfig = NULL;
+
+/*
+ * GUC default_text_search_config, and a cache of the current config's OID
+ */
+char *TSCurrentConfig = NULL;
+
+static Oid TSCurrentConfigCache = InvalidOid;
+
+
+/*
+ * 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;
+
+ hash_seq_init(&status, hash);
+ while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL)
+ entry->isvalid = false;
+
+ /* Also invalidate the current-config cache if it's pg_ts_config */
+ if (hash == TSConfigCacheHash)
+ TSCurrentConfigCache = InvalidOid;
+}
+
+/*
+ * Fetch parser cache entry
+ */
+TSParserCacheEntry *
+lookup_ts_parser_cache(Oid prsId)
+{
+ TSParserCacheEntry *entry;
+
+ if (TSParserCacheHash == NULL)
+ {
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(TSParserCacheEntry);
+ TSParserCacheHash = hash_create("Tsearch parser cache", 4,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+ /* Flush cache on pg_ts_parser changes */
+ CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack,
+ PointerGetDatum(TSParserCacheHash));
+
+ /* Also make sure CacheMemoryContext exists */
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+ }
+
+ /* Check single-entry cache */
+ if (lastUsedParser && lastUsedParser->prsId == prsId &&
+ lastUsedParser->isvalid)
+ return lastUsedParser;
+
+ /* Try to look up an existing entry */
+ entry = (TSParserCacheEntry *) hash_search(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))
+ elog(ERROR, "cache lookup failed for text search parser %u",
+ prsId);
+ prs = (Form_pg_ts_parser) GETSTRUCT(tp);
+
+ /*
+ * Sanity checks
+ */
+ if (!OidIsValid(prs->prsstart))
+ elog(ERROR, "text search parser %u has no prsstart method", prsId);
+ if (!OidIsValid(prs->prstoken))
+ elog(ERROR, "text search parser %u has no prstoken method", prsId);
+ if (!OidIsValid(prs->prsend))
+ elog(ERROR, "text search parser %u has no prsend method", prsId);
+
+ if (entry == NULL)
+ {
+ bool found;
+
+ /* Now make the cache entry */
+ entry = (TSParserCacheEntry *)
+ hash_search(TSParserCacheHash,
+ (void *) &prsId,
+ HASH_ENTER, &found);
+ Assert(!found); /* it wasn't there a moment ago */
+ }
+
+ MemSet(entry, 0, sizeof(TSParserCacheEntry));
+ 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, CacheMemoryContext);
+ fmgr_info_cxt(entry->tokenOid, &entry->prstoken, CacheMemoryContext);
+ fmgr_info_cxt(entry->endOid, &entry->prsend, CacheMemoryContext);
+ if (OidIsValid(entry->headlineOid))
+ fmgr_info_cxt(entry->headlineOid, &entry->prsheadline,
+ CacheMemoryContext);
+
+ entry->isvalid = true;
+ }
+
+ lastUsedParser = entry;
+
+ return entry;
+}
+
+/*
+ * Fetch dictionary cache entry
+ */
+TSDictionaryCacheEntry *
+lookup_ts_dictionary_cache(Oid dictId)
+{
+ TSDictionaryCacheEntry *entry;
+
+ if (TSDictionaryCacheHash == NULL)
+ {
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(TSDictionaryCacheEntry);
+ TSDictionaryCacheHash = hash_create("Tsearch dictionary cache", 8,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+ /* Flush cache on pg_ts_dict and pg_ts_template changes */
+ CacheRegisterSyscacheCallback(TSDICTOID, InvalidateTSCacheCallBack,
+ PointerGetDatum(TSDictionaryCacheHash));
+ CacheRegisterSyscacheCallback(TSTEMPLATEOID, InvalidateTSCacheCallBack,
+ PointerGetDatum(TSDictionaryCacheHash));
+
+ /* Also make sure CacheMemoryContext exists */
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+ }
+
+ /* Check single-entry cache */
+ if (lastUsedDictionary && lastUsedDictionary->dictId == dictId &&
+ lastUsedDictionary->isvalid)
+ return lastUsedDictionary;
+
+ /* Try to look up an existing entry */
+ entry = (TSDictionaryCacheEntry *) hash_search(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 template;
+ MemoryContext saveCtx;
+
+ tpdict = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
+ if (!HeapTupleIsValid(tpdict))
+ elog(ERROR, "cache lookup failed for text search dictionary %u",
+ dictId);
+ dict = (Form_pg_ts_dict) GETSTRUCT(tpdict);
+
+ /*
+ * Sanity checks
+ */
+ if (!OidIsValid(dict->dicttemplate))
+ elog(ERROR, "text search dictionary %u has no template", dictId);
+
+ /*
+ * Retrieve dictionary's template
+ */
+ tptmpl = SearchSysCache1(TSTEMPLATEOID,
+ ObjectIdGetDatum(dict->dicttemplate));
+ if (!HeapTupleIsValid(tptmpl))
+ elog(ERROR, "cache lookup failed for text search template %u",
+ dict->dicttemplate);
+ template = (Form_pg_ts_template) GETSTRUCT(tptmpl);
+
+ /*
+ * Sanity checks
+ */
+ if (!OidIsValid(template->tmpllexize))
+ elog(ERROR, "text search template %u has no lexize method",
+ template->tmpllexize);
+
+ if (entry == NULL)
+ {
+ bool found;
+
+ /* Now make the cache entry */
+ entry = (TSDictionaryCacheEntry *)
+ hash_search(TSDictionaryCacheHash,
+ (void *) &dictId,
+ HASH_ENTER, &found);
+ Assert(!found); /* it wasn't there a moment ago */
+
+ /* Create private memory context the first time through */
+ saveCtx = AllocSetContextCreate(CacheMemoryContext,
+ "TS dictionary",
+ ALLOCSET_SMALL_SIZES);
+ MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname));
+ }
+ else
+ {
+ /* Clear the existing entry's private context */
+ saveCtx = entry->dictCtx;
+ /* Don't let context's ident pointer dangle while we reset it */
+ MemoryContextSetIdentifier(saveCtx, NULL);
+ MemoryContextReset(saveCtx);
+ MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname));
+ }
+
+ MemSet(entry, 0, sizeof(TSDictionaryCacheEntry));
+ entry->dictId = dictId;
+ entry->dictCtx = saveCtx;
+
+ entry->lexizeOid = template->tmpllexize;
+
+ if (OidIsValid(template->tmplinit))
+ {
+ List *dictoptions;
+ Datum opt;
+ bool isnull;
+ 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(template->tmplinit,
+ PointerGetDatum(dictoptions)));
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ ReleaseSysCache(tptmpl);
+ ReleaseSysCache(tpdict);
+
+ fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
+
+ entry->isvalid = true;
+ }
+
+ 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 TSCurrentConfigCache, too.
+ */
+static void
+init_ts_config_cache(void)
+{
+ HASHCTL ctl;
+
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(TSConfigCacheEntry);
+ TSConfigCacheHash = hash_create("Tsearch configuration cache", 16,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+ /* Flush cache on pg_ts_config and pg_ts_config_map changes */
+ CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack,
+ PointerGetDatum(TSConfigCacheHash));
+ CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack,
+ PointerGetDatum(TSConfigCacheHash));
+
+ /* Also make sure CacheMemoryContext exists */
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+}
+
+/*
+ * Fetch configuration cache entry
+ */
+TSConfigCacheEntry *
+lookup_ts_config_cache(Oid cfgId)
+{
+ TSConfigCacheEntry *entry;
+
+ if (TSConfigCacheHash == NULL)
+ {
+ /* First time through: initialize the hash table */
+ init_ts_config_cache();
+ }
+
+ /* Check single-entry cache */
+ if (lastUsedConfig && lastUsedConfig->cfgId == cfgId &&
+ lastUsedConfig->isvalid)
+ return lastUsedConfig;
+
+ /* Try to look up an existing entry */
+ entry = (TSConfigCacheEntry *) hash_search(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;
+ int i;
+
+ tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for text search configuration %u",
+ cfgId);
+ cfg = (Form_pg_ts_config) GETSTRUCT(tp);
+
+ /*
+ * Sanity checks
+ */
+ if (!OidIsValid(cfg->cfgparser))
+ elog(ERROR, "text search configuration %u has no parser", cfgId);
+
+ if (entry == NULL)
+ {
+ bool found;
+
+ /* Now make the cache entry */
+ entry = (TSConfigCacheEntry *)
+ hash_search(TSConfigCacheHash,
+ (void *) &cfgId,
+ HASH_ENTER, &found);
+ Assert(!found); /* it wasn't there a moment ago */
+ }
+ else
+ {
+ /* Cleanup old contents */
+ if (entry->map)
+ {
+ for (i = 0; i < entry->lenmap; i++)
+ if (entry->map[i].dictIds)
+ pfree(entry->map[i].dictIds);
+ pfree(entry->map);
+ }
+ }
+
+ MemSet(entry, 0, sizeof(TSConfigCacheEntry));
+ entry->cfgId = cfgId;
+ entry->prsId = cfg->cfgparser;
+
+ 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.
+ */
+ MemSet(maplists, 0, sizeof(maplists));
+ maxtokentype = 0;
+ ndicts = 0;
+
+ ScanKeyInit(&mapskey,
+ Anum_pg_ts_config_map_mapcfg,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(cfgId));
+
+ maprel = table_open(TSConfigMapRelationId, AccessShareLock);
+ mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
+ mapscan = systable_beginscan_ordered(maprel, mapidx,
+ NULL, 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)
+ elog(ERROR, "maptokentype value %d is out of range", toktype);
+ if (toktype < maxtokentype)
+ elog(ERROR, "maptokentype entries are out of order");
+ if (toktype > maxtokentype)
+ {
+ /* starting a new token type, but first save the prior data */
+ if (ndicts > 0)
+ {
+ maplists[maxtokentype].len = ndicts;
+ maplists[maxtokentype].dictIds = (Oid *)
+ MemoryContextAlloc(CacheMemoryContext,
+ sizeof(Oid) * ndicts);
+ memcpy(maplists[maxtokentype].dictIds, mapdicts,
+ sizeof(Oid) * ndicts);
+ }
+ maxtokentype = toktype;
+ mapdicts[0] = cfgmap->mapdict;
+ ndicts = 1;
+ }
+ else
+ {
+ /* continuing data for current token type */
+ if (ndicts >= MAXDICTSPERTT)
+ elog(ERROR, "too many pg_ts_config_map entries for one token type");
+ mapdicts[ndicts++] = cfgmap->mapdict;
+ }
+ }
+
+ systable_endscan_ordered(mapscan);
+ index_close(mapidx, AccessShareLock);
+ table_close(maprel, AccessShareLock);
+
+ if (ndicts > 0)
+ {
+ /* save the last token type's dictionaries */
+ maplists[maxtokentype].len = ndicts;
+ maplists[maxtokentype].dictIds = (Oid *)
+ MemoryContextAlloc(CacheMemoryContext,
+ sizeof(Oid) * ndicts);
+ memcpy(maplists[maxtokentype].dictIds, mapdicts,
+ sizeof(Oid) * ndicts);
+ /* and save the overall map */
+ entry->lenmap = maxtokentype + 1;
+ entry->map = (ListDictionary *)
+ MemoryContextAlloc(CacheMemoryContext,
+ sizeof(ListDictionary) * entry->lenmap);
+ memcpy(entry->map, maplists,
+ sizeof(ListDictionary) * entry->lenmap);
+ }
+
+ entry->isvalid = true;
+ }
+
+ lastUsedConfig = entry;
+
+ return entry;
+}
+
+
+/*---------------------------------------------------
+ * GUC variable "default_text_search_config"
+ *---------------------------------------------------
+ */
+
+Oid
+getTSCurrentConfig(bool emitError)
+{
+ /* if we have a cached value, return it */
+ if (OidIsValid(TSCurrentConfigCache))
+ return TSCurrentConfigCache;
+
+ /* fail if GUC hasn't been set up yet */
+ if (TSCurrentConfig == NULL || *TSCurrentConfig == '\0')
+ {
+ if (emitError)
+ elog(ERROR, "text search configuration isn't set");
+ else
+ return InvalidOid;
+ }
+
+ if (TSConfigCacheHash == NULL)
+ {
+ /* First time through: initialize the tsconfig inval callback */
+ init_ts_config_cache();
+ }
+
+ /* Look up the config */
+ TSCurrentConfigCache =
+ get_ts_config_oid(stringToQualifiedNameList(TSCurrentConfig),
+ !emitError);
+
+ return TSCurrentConfigCache;
+}
+
+/* GUC check_hook for default_text_search_config */
+bool
+check_TSCurrentConfig(char **newval, void **extra, GucSource source)
+{
+ /*
+ * If we aren't inside a transaction, or connected to a database, we
+ * cannot do the catalog accesses necessary to verify the config name.
+ * Must accept it on faith.
+ */
+ if (IsTransactionState() && MyDatabaseId != InvalidOid)
+ {
+ Oid cfgId;
+ HeapTuple tuple;
+ Form_pg_ts_config cfg;
+ char *buf;
+
+ cfgId = get_ts_config_oid(stringToQualifiedNameList(*newval), true);
+
+ /*
+ * When source == PGC_S_TEST, don't throw a hard error for a
+ * nonexistent configuration, only a NOTICE. See comments in guc.h.
+ */
+ if (!OidIsValid(cfgId))
+ {
+ 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(cfgId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for text search configuration %u",
+ cfgId);
+ cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
+
+ buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace),
+ NameStr(cfg->cfgname));
+
+ ReleaseSysCache(tuple);
+
+ /* GUC wants it malloc'd not palloc'd */
+ free(*newval);
+ *newval = strdup(buf);
+ pfree(buf);
+ if (!*newval)
+ return false;
+ }
+
+ return true;
+}
+
+/* GUC assign_hook for default_text_search_config */
+void
+assign_TSCurrentConfig(const char *newval, void *extra)
+{
+ /* Just reset the cache to force a lookup on first use */
+ TSCurrentConfigCache = InvalidOid;
+}