
Some of these appear to be leftovers from when hash_search() took a char * argument (changed in 5999e78fc45dcb91784b64b6e9ae43f4e4f68ca2). Since after this there is some more horizontal space available, do some light reformatting where suitable. Reviewed-by: Corey Huinker <corey.huinker@gmail.com> Discussion: https://www.postgresql.org/message-id/flat/fd9adf5d-b1aa-e82f-e4c7-263c30145807%40enterprisedb.com
675 lines
18 KiB
C
675 lines
18 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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-2023, 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 "nodes/miscnodes.h"
|
|
#include "tsearch/ts_cache.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/catcache.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/guc_hooks.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,
|
|
&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, &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,
|
|
&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,
|
|
&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,
|
|
&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,
|
|
&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)
|
|
{
|
|
List *namelist;
|
|
|
|
/* 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 */
|
|
if (emitError)
|
|
{
|
|
namelist = stringToQualifiedNameList(TSCurrentConfig, NULL);
|
|
TSCurrentConfigCache = get_ts_config_oid(namelist, false);
|
|
}
|
|
else
|
|
{
|
|
ErrorSaveContext escontext = {T_ErrorSaveContext};
|
|
|
|
namelist = stringToQualifiedNameList(TSCurrentConfig,
|
|
(Node *) &escontext);
|
|
if (namelist != NIL)
|
|
TSCurrentConfigCache = get_ts_config_oid(namelist, true);
|
|
else
|
|
TSCurrentConfigCache = InvalidOid; /* bad name list syntax */
|
|
}
|
|
|
|
return TSCurrentConfigCache;
|
|
}
|
|
|
|
/* GUC check_hook for default_text_search_config */
|
|
bool
|
|
check_default_text_search_config(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)
|
|
{
|
|
ErrorSaveContext escontext = {T_ErrorSaveContext};
|
|
List *namelist;
|
|
Oid cfgId;
|
|
HeapTuple tuple;
|
|
Form_pg_ts_config cfg;
|
|
char *buf;
|
|
|
|
namelist = stringToQualifiedNameList(*newval,
|
|
(Node *) &escontext);
|
|
if (namelist != NIL)
|
|
cfgId = get_ts_config_oid(namelist, true);
|
|
else
|
|
cfgId = InvalidOid; /* bad name list syntax */
|
|
|
|
/*
|
|
* 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 guc_malloc'd not palloc'd */
|
|
guc_free(*newval);
|
|
*newval = guc_strdup(LOG, buf);
|
|
pfree(buf);
|
|
if (!*newval)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* GUC assign_hook for default_text_search_config */
|
|
void
|
|
assign_default_text_search_config(const char *newval, void *extra)
|
|
{
|
|
/* Just reset the cache to force a lookup on first use */
|
|
TSCurrentConfigCache = InvalidOid;
|
|
}
|