postgresql/src/backend/utils/cache/ts_cache.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

653 lines
18 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* 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
2010-09-20 16:08:53 -04:00
* 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,
Improve hash_create's API for selecting simple-binary-key hash functions. Previously, if you wanted anything besides C-string hash keys, you had to specify a custom hashing function to hash_create(). Nearly all such callers were specifying tag_hash or oid_hash; which is tedious, and rather error-prone, since a caller could easily miss the opportunity to optimize by using hash_uint32 when appropriate. Replace this with a design whereby callers using simple binary-data keys just specify HASH_BLOBS and don't need to mess with specific support functions. hash_create() itself will take care of optimizing when the key size is four bytes. This nets out saving a few hundred bytes of code space, and offers a measurable performance improvement in tidbitmap.c (which was not exploiting the opportunity to use hash_uint32 for its 4-byte keys). There might be some wins elsewhere too, I didn't analyze closely. In future we could look into offering a similar optimized hashing function for 8-byte keys. Under this design that could be done in a centralized and machine-independent fashion, whereas getting it right for keys of platform-dependent sizes would've been notationally painful before. For the moment, the old way still works fine, so as not to break source code compatibility for loadable modules. Eventually we might want to remove tag_hash and friends from the exported API altogether, since there's no real need for them to be explicitly referenced from outside dynahash.c. Teodor Sigaev and Tom Lane
2014-12-18 13:36:29 -05:00
&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,
Improve hash_create's API for selecting simple-binary-key hash functions. Previously, if you wanted anything besides C-string hash keys, you had to specify a custom hashing function to hash_create(). Nearly all such callers were specifying tag_hash or oid_hash; which is tedious, and rather error-prone, since a caller could easily miss the opportunity to optimize by using hash_uint32 when appropriate. Replace this with a design whereby callers using simple binary-data keys just specify HASH_BLOBS and don't need to mess with specific support functions. hash_create() itself will take care of optimizing when the key size is four bytes. This nets out saving a few hundred bytes of code space, and offers a measurable performance improvement in tidbitmap.c (which was not exploiting the opportunity to use hash_uint32 for its 4-byte keys). There might be some wins elsewhere too, I didn't analyze closely. In future we could look into offering a similar optimized hashing function for 8-byte keys. Under this design that could be done in a centralized and machine-independent fashion, whereas getting it right for keys of platform-dependent sizes would've been notationally painful before. For the moment, the old way still works fine, so as not to break source code compatibility for loadable modules. Eventually we might want to remove tag_hash and friends from the exported API altogether, since there's no real need for them to be explicitly referenced from outside dynahash.c. Teodor Sigaev and Tom Lane
2014-12-18 13:36:29 -05:00
&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 */
Allow memory contexts to have both fixed and variable ident strings. Originally, we treated memory context names as potentially variable in all cases, and therefore always copied them into the context header. Commit 9fa6f00b1 rethought this a little bit and invented a distinction between fixed and variable names, skipping the copy step for the former. But we can make things both simpler and more useful by instead allowing there to be two parts to a context's identification, a fixed "name" and an optional, variable "ident". The name supplied in the context create call is now required to be a compile-time-constant string in all cases, as it is never copied but just pointed to. The "ident" string, if wanted, is supplied later. This is needed because typically we want the ident to be stored inside the context so that it's cleaned up automatically on context deletion; that means it has to be copied into the context before we can set the pointer. The cost of this approach is basically just an additional pointer field in struct MemoryContextData, which isn't much overhead, and is bought back entirely in the AllocSet case by not needing a headerSize field anymore, since we no longer have to cope with variable header length. In addition, we can simplify the internal interfaces for memory context creation still further, saving a few cycles there. And it's no longer true that a custom identifier disqualifies a context from participating in aset.c's freelist scheme, so possibly there's some win on that end. All the places that were using non-compile-time-constant context names are adjusted to put the variable info into the "ident" instead. This allows more effective identification of those contexts in many cases; for example, subsidary contexts of relcache entries are now identified by both type (e.g. "index info") and relname, where before you got only one or the other. Contexts associated with PL function cache entries are now identified more fully and uniformly, too. I also arranged for plancache contexts to use the query source string as their identifier. This is basically free for CachedPlanSources, as they contained a copy of that string already. We pay an extra pstrdup to do it for CachedPlans. That could perhaps be avoided, but it would make things more fragile (since the CachedPlanSource is sometimes destroyed first). I suspect future improvements in error reporting will require CachedPlans to have a copy of that string anyway, so it's not clear that it's worth moving mountains to avoid it now. This also changes the APIs for context statistics routines so that the context-specific routines no longer assume that output goes straight to stderr, nor do they know all details of the output format. This is useful immediately to reduce code duplication, and it also allows for external code to do something with stats output that's different from printing to stderr. The reason for pushing this now rather than waiting for v12 is that it rethinks some of the API changes made by commit 9fa6f00b1. Seems better for extension authors to endure just one round of API changes not two. Discussion: https://postgr.es/m/CAB=Je-FdtmFZ9y9REHD7VsSrnCkiBhsA4mdsLKSPauwXtQBeNA@mail.gmail.com
2018-03-27 16:46:47 -04:00
saveCtx = AllocSetContextCreate(CacheMemoryContext,
"TS dictionary",
ALLOCSET_SMALL_SIZES);
MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname));
}
else
{
/* Clear the existing entry's private context */
saveCtx = entry->dictCtx;
Allow memory contexts to have both fixed and variable ident strings. Originally, we treated memory context names as potentially variable in all cases, and therefore always copied them into the context header. Commit 9fa6f00b1 rethought this a little bit and invented a distinction between fixed and variable names, skipping the copy step for the former. But we can make things both simpler and more useful by instead allowing there to be two parts to a context's identification, a fixed "name" and an optional, variable "ident". The name supplied in the context create call is now required to be a compile-time-constant string in all cases, as it is never copied but just pointed to. The "ident" string, if wanted, is supplied later. This is needed because typically we want the ident to be stored inside the context so that it's cleaned up automatically on context deletion; that means it has to be copied into the context before we can set the pointer. The cost of this approach is basically just an additional pointer field in struct MemoryContextData, which isn't much overhead, and is bought back entirely in the AllocSet case by not needing a headerSize field anymore, since we no longer have to cope with variable header length. In addition, we can simplify the internal interfaces for memory context creation still further, saving a few cycles there. And it's no longer true that a custom identifier disqualifies a context from participating in aset.c's freelist scheme, so possibly there's some win on that end. All the places that were using non-compile-time-constant context names are adjusted to put the variable info into the "ident" instead. This allows more effective identification of those contexts in many cases; for example, subsidary contexts of relcache entries are now identified by both type (e.g. "index info") and relname, where before you got only one or the other. Contexts associated with PL function cache entries are now identified more fully and uniformly, too. I also arranged for plancache contexts to use the query source string as their identifier. This is basically free for CachedPlanSources, as they contained a copy of that string already. We pay an extra pstrdup to do it for CachedPlans. That could perhaps be avoided, but it would make things more fragile (since the CachedPlanSource is sometimes destroyed first). I suspect future improvements in error reporting will require CachedPlans to have a copy of that string anyway, so it's not clear that it's worth moving mountains to avoid it now. This also changes the APIs for context statistics routines so that the context-specific routines no longer assume that output goes straight to stderr, nor do they know all details of the output format. This is useful immediately to reduce code duplication, and it also allows for external code to do something with stats output that's different from printing to stderr. The reason for pushing this now rather than waiting for v12 is that it rethinks some of the API changes made by commit 9fa6f00b1. Seems better for extension authors to endure just one round of API changes not two. Discussion: https://postgr.es/m/CAB=Je-FdtmFZ9y9REHD7VsSrnCkiBhsA4mdsLKSPauwXtQBeNA@mail.gmail.com
2018-03-27 16:46:47 -04:00
/* 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,
Improve hash_create's API for selecting simple-binary-key hash functions. Previously, if you wanted anything besides C-string hash keys, you had to specify a custom hashing function to hash_create(). Nearly all such callers were specifying tag_hash or oid_hash; which is tedious, and rather error-prone, since a caller could easily miss the opportunity to optimize by using hash_uint32 when appropriate. Replace this with a design whereby callers using simple binary-data keys just specify HASH_BLOBS and don't need to mess with specific support functions. hash_create() itself will take care of optimizing when the key size is four bytes. This nets out saving a few hundred bytes of code space, and offers a measurable performance improvement in tidbitmap.c (which was not exploiting the opportunity to use hash_uint32 for its 4-byte keys). There might be some wins elsewhere too, I didn't analyze closely. In future we could look into offering a similar optimized hashing function for 8-byte keys. Under this design that could be done in a centralized and machine-independent fashion, whereas getting it right for keys of platform-dependent sizes would've been notationally painful before. For the moment, the old way still works fine, so as not to break source code compatibility for loadable modules. Eventually we might want to remove tag_hash and friends from the exported API altogether, since there's no real need for them to be explicitly referenced from outside dynahash.c. Teodor Sigaev and Tom Lane
2014-12-18 13:36:29 -05:00
&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;
}