2007-08-20 21:11:32 -04:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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
|
2007-11-15 16:14:46 -05:00
|
|
|
* if a cache inval and reval happens! This does not look like it will be
|
2007-08-20 21:11:32 -04:00
|
|
|
* a big problem as long as parser and dictionary methods do not attempt
|
|
|
|
|
* any database access.
|
|
|
|
|
*
|
|
|
|
|
*
|
2017-01-03 13:48:53 -05:00
|
|
|
* Copyright (c) 2006-2017, PostgreSQL Global Development Group
|
2007-08-20 21:11:32 -04:00
|
|
|
*
|
|
|
|
|
* IDENTIFICATION
|
2010-09-20 16:08:53 -04:00
|
|
|
* src/backend/utils/cache/ts_cache.c
|
2007-08-20 21:11:32 -04:00
|
|
|
*
|
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
|
|
#include "access/genam.h"
|
|
|
|
|
#include "access/heapam.h"
|
2012-08-30 16:15:44 -04:00
|
|
|
#include "access/htup_details.h"
|
2007-08-20 21:11:32 -04:00
|
|
|
#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"
|
2007-08-21 21:39:46 -04:00
|
|
|
#include "commands/defrem.h"
|
2007-08-20 21:11:32 -04:00
|
|
|
#include "tsearch/ts_cache.h"
|
|
|
|
|
#include "utils/builtins.h"
|
2012-08-28 18:26:24 -04:00
|
|
|
#include "utils/catcache.h"
|
2007-08-20 21:11:32 -04:00
|
|
|
#include "utils/fmgroids.h"
|
|
|
|
|
#include "utils/inval.h"
|
|
|
|
|
#include "utils/lsyscache.h"
|
|
|
|
|
#include "utils/memutils.h"
|
2017-01-20 20:29:53 -05:00
|
|
|
#include "utils/regproc.h"
|
2007-08-20 21:11:32 -04:00
|
|
|
#include "utils/syscache.h"
|
2008-03-26 17:10:39 -04:00
|
|
|
#include "utils/tqual.h"
|
2007-08-20 21:11:32 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2008-09-09 14:58:09 -04:00
|
|
|
* We use this syscache callback to detect when a visible change to a TS
|
2007-08-20 21:11:32 -04:00
|
|
|
* catalog entry has been made, by either our own backend or another one.
|
2008-09-09 14:58:09 -04:00
|
|
|
*
|
|
|
|
|
* 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.
|
2007-08-20 21:11:32 -04:00
|
|
|
*
|
|
|
|
|
* We can use the same function for all TS caches by passing the hash
|
|
|
|
|
* table address as the "arg".
|
|
|
|
|
*/
|
|
|
|
|
static void
|
2011-08-16 19:27:46 -04:00
|
|
|
InvalidateTSCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
|
2007-08-20 21:11:32 -04:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
MemSet(&ctl, 0, sizeof(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);
|
2007-08-20 21:11:32 -04:00
|
|
|
/* Flush cache on pg_ts_parser changes */
|
|
|
|
|
CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack,
|
|
|
|
|
PointerGetDatum(TSParserCacheHash));
|
2009-12-27 13:55:52 -05:00
|
|
|
|
|
|
|
|
/* Also make sure CacheMemoryContext exists */
|
|
|
|
|
if (!CacheMemoryContext)
|
|
|
|
|
CreateCacheMemoryContext();
|
2007-08-20 21:11:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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)
|
|
|
|
|
{
|
|
|
|
|
/*
|
2007-11-15 16:14:46 -05:00
|
|
|
* If we didn't find one, we want to make one. But first look up the
|
|
|
|
|
* object to be sure the OID is real.
|
2007-08-20 21:11:32 -04:00
|
|
|
*/
|
|
|
|
|
HeapTuple tp;
|
|
|
|
|
Form_pg_ts_parser prs;
|
|
|
|
|
|
2010-02-14 13:42:19 -05:00
|
|
|
tp = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId));
|
2007-08-20 21:11:32 -04:00
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
MemSet(&ctl, 0, sizeof(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);
|
2007-08-20 21:11:32 -04:00
|
|
|
/* Flush cache on pg_ts_dict and pg_ts_template changes */
|
|
|
|
|
CacheRegisterSyscacheCallback(TSDICTOID, InvalidateTSCacheCallBack,
|
|
|
|
|
PointerGetDatum(TSDictionaryCacheHash));
|
|
|
|
|
CacheRegisterSyscacheCallback(TSTEMPLATEOID, InvalidateTSCacheCallBack,
|
|
|
|
|
PointerGetDatum(TSDictionaryCacheHash));
|
2009-12-27 13:55:52 -05:00
|
|
|
|
|
|
|
|
/* Also make sure CacheMemoryContext exists */
|
|
|
|
|
if (!CacheMemoryContext)
|
|
|
|
|
CreateCacheMemoryContext();
|
2007-08-20 21:11:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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)
|
|
|
|
|
{
|
|
|
|
|
/*
|
2007-11-15 16:14:46 -05:00
|
|
|
* If we didn't find one, we want to make one. But first look up the
|
|
|
|
|
* object to be sure the OID is real.
|
2007-08-20 21:11:32 -04:00
|
|
|
*/
|
|
|
|
|
HeapTuple tpdict,
|
|
|
|
|
tptmpl;
|
|
|
|
|
Form_pg_ts_dict dict;
|
|
|
|
|
Form_pg_ts_template template;
|
2007-08-21 21:39:46 -04:00
|
|
|
MemoryContext saveCtx;
|
2007-08-20 21:11:32 -04:00
|
|
|
|
2010-02-14 13:42:19 -05:00
|
|
|
tpdict = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
|
2007-08-20 21:11:32 -04:00
|
|
|
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
|
|
|
|
|
*/
|
2010-02-14 13:42:19 -05:00
|
|
|
tptmpl = SearchSysCache1(TSTEMPLATEOID,
|
|
|
|
|
ObjectIdGetDatum(dict->dicttemplate));
|
2007-08-20 21:11:32 -04:00
|
|
|
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,
|
|
|
|
|
NameStr(dict->dictname),
|
Add macros to make AllocSetContextCreate() calls simpler and safer.
I found that half a dozen (nearly 5%) of our AllocSetContextCreate calls
had typos in the context-sizing parameters. While none of these led to
especially significant problems, they did create minor inefficiencies,
and it's now clear that expecting people to copy-and-paste those calls
accurately is not a great idea. Let's reduce the risk of future errors
by introducing single macros that encapsulate the common use-cases.
Three such macros are enough to cover all but two special-purpose contexts;
those two calls can be left as-is, I think.
While this patch doesn't in itself improve matters for third-party
extensions, it doesn't break anything for them either, and they can
gradually adopt the simplified notation over time.
In passing, change TopMemoryContext to use the default allocation
parameters. Formerly it could only be extended 8K at a time. That was
probably reasonable when this code was written; but nowadays we create
many more contexts than we did then, so that it's not unusual to have a
couple hundred K in TopMemoryContext, even without considering various
dubious code that sticks other things there. There seems no good reason
not to let it use growing blocks like most other contexts.
Back-patch to 9.6, mostly because that's still close enough to HEAD that
it's easy to do so, and keeping the branches in sync can be expected to
avoid some future back-patching pain. The bugs fixed by these changes
don't seem to be significant enough to justify fixing them further back.
Discussion: <21072.1472321324@sss.pgh.pa.us>
2016-08-27 17:50:38 -04:00
|
|
|
ALLOCSET_SMALL_SIZES);
|
2007-08-20 21:11:32 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Clear the existing entry's private context */
|
|
|
|
|
saveCtx = entry->dictCtx;
|
|
|
|
|
MemoryContextResetAndDeleteChildren(saveCtx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MemSet(entry, 0, sizeof(TSDictionaryCacheEntry));
|
|
|
|
|
entry->dictId = dictId;
|
|
|
|
|
entry->dictCtx = saveCtx;
|
|
|
|
|
|
|
|
|
|
entry->lexizeOid = template->tmpllexize;
|
|
|
|
|
|
|
|
|
|
if (OidIsValid(template->tmplinit))
|
|
|
|
|
{
|
2007-08-21 21:39:46 -04:00
|
|
|
List *dictoptions;
|
2007-08-20 21:11:32 -04:00
|
|
|
Datum opt;
|
2007-08-21 21:39:46 -04:00
|
|
|
bool isnull;
|
|
|
|
|
MemoryContext oldcontext;
|
|
|
|
|
|
|
|
|
|
/*
|
2007-11-15 16:14:46 -05:00
|
|
|
* Init method runs in dictionary's private memory context, and we
|
|
|
|
|
* make sure the options are stored there too
|
2007-08-21 21:39:46 -04:00
|
|
|
*/
|
|
|
|
|
oldcontext = MemoryContextSwitchTo(entry->dictCtx);
|
2007-08-20 21:11:32 -04:00
|
|
|
|
|
|
|
|
opt = SysCacheGetAttr(TSDICTOID, tpdict,
|
|
|
|
|
Anum_pg_ts_dict_dictinitoption,
|
|
|
|
|
&isnull);
|
|
|
|
|
if (isnull)
|
2007-08-21 21:39:46 -04:00
|
|
|
dictoptions = NIL;
|
|
|
|
|
else
|
|
|
|
|
dictoptions = deserialize_deflist(opt);
|
2007-08-20 21:11:32 -04:00
|
|
|
|
2007-08-21 21:39:46 -04:00
|
|
|
entry->dictData =
|
|
|
|
|
DatumGetPointer(OidFunctionCall1(template->tmplinit,
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 15:35:54 -04:00
|
|
|
PointerGetDatum(dictoptions)));
|
2007-08-21 21:39:46 -04:00
|
|
|
|
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
2007-08-20 21:11:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
MemSet(&ctl, 0, sizeof(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);
|
2007-08-20 21:11:32 -04:00
|
|
|
/* Flush cache on pg_ts_config and pg_ts_config_map changes */
|
|
|
|
|
CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack,
|
|
|
|
|
PointerGetDatum(TSConfigCacheHash));
|
|
|
|
|
CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack,
|
|
|
|
|
PointerGetDatum(TSConfigCacheHash));
|
2009-12-27 13:55:52 -05:00
|
|
|
|
|
|
|
|
/* Also make sure CacheMemoryContext exists */
|
|
|
|
|
if (!CacheMemoryContext)
|
|
|
|
|
CreateCacheMemoryContext();
|
2007-08-20 21:11:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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)
|
|
|
|
|
{
|
|
|
|
|
/*
|
2007-11-15 16:14:46 -05:00
|
|
|
* If we didn't find one, we want to make one. But first look up the
|
|
|
|
|
* object to be sure the OID is real.
|
2007-08-20 21:11:32 -04:00
|
|
|
*/
|
|
|
|
|
HeapTuple tp;
|
|
|
|
|
Form_pg_ts_config cfg;
|
|
|
|
|
Relation maprel;
|
|
|
|
|
Relation mapidx;
|
|
|
|
|
ScanKeyData mapskey;
|
2008-04-12 19:14:21 -04:00
|
|
|
SysScanDesc mapscan;
|
2007-08-20 21:11:32 -04:00
|
|
|
HeapTuple maptup;
|
|
|
|
|
ListDictionary maplists[MAXTOKENTYPE + 1];
|
|
|
|
|
Oid mapdicts[MAXDICTSPERTT];
|
|
|
|
|
int maxtokentype;
|
|
|
|
|
int ndicts;
|
|
|
|
|
int i;
|
|
|
|
|
|
2010-02-14 13:42:19 -05:00
|
|
|
tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
|
2007-08-20 21:11:32 -04:00
|
|
|
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 = heap_open(TSConfigMapRelationId, AccessShareLock);
|
|
|
|
|
mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
|
2008-04-12 19:14:21 -04:00
|
|
|
mapscan = systable_beginscan_ordered(maprel, mapidx,
|
Use an MVCC snapshot, rather than SnapshotNow, for catalog scans.
SnapshotNow scans have the undesirable property that, in the face of
concurrent updates, the scan can fail to see either the old or the new
versions of the row. In many cases, we work around this by requiring
DDL operations to hold AccessExclusiveLock on the object being
modified; in some cases, the existing locking is inadequate and random
failures occur as a result. This commit doesn't change anything
related to locking, but will hopefully pave the way to allowing lock
strength reductions in the future.
The major issue has held us back from making this change in the past
is that taking an MVCC snapshot is significantly more expensive than
using a static special snapshot such as SnapshotNow. However, testing
of various worst-case scenarios reveals that this problem is not
severe except under fairly extreme workloads. To mitigate those
problems, we avoid retaking the MVCC snapshot for each new scan;
instead, we take a new snapshot only when invalidation messages have
been processed. The catcache machinery already requires that
invalidation messages be sent before releasing the related heavyweight
lock; else other backends might rely on locally-cached data rather
than scanning the catalog at all. Thus, making snapshot reuse
dependent on the same guarantees shouldn't break anything that wasn't
already subtly broken.
Patch by me. Review by Michael Paquier and Andres Freund.
2013-07-02 09:47:01 -04:00
|
|
|
NULL, 1, &mapskey);
|
2007-08-20 21:11:32 -04:00
|
|
|
|
2008-04-12 19:14:21 -04:00
|
|
|
while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL)
|
2007-08-20 21:11:32 -04:00
|
|
|
{
|
|
|
|
|
Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
|
2007-11-15 16:14:46 -05:00
|
|
|
int toktype = cfgmap->maptokentype;
|
2007-08-20 21:11:32 -04:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2008-04-12 19:14:21 -04:00
|
|
|
systable_endscan_ordered(mapscan);
|
2007-08-20 21:11:32 -04:00
|
|
|
index_close(mapidx, AccessShareLock);
|
|
|
|
|
heap_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 =
|
2010-08-05 11:25:36 -04:00
|
|
|
get_ts_config_oid(stringToQualifiedNameList(TSCurrentConfig),
|
2011-04-10 11:42:00 -04:00
|
|
|
!emitError);
|
2007-08-20 21:11:32 -04:00
|
|
|
|
|
|
|
|
return TSCurrentConfigCache;
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-07 00:11:01 -04:00
|
|
|
/* GUC check_hook for default_text_search_config */
|
|
|
|
|
bool
|
|
|
|
|
check_TSCurrentConfig(char **newval, void **extra, GucSource source)
|
2007-08-20 21:11:32 -04:00
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* 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 cfgId;
|
|
|
|
|
HeapTuple tuple;
|
|
|
|
|
Form_pg_ts_config cfg;
|
|
|
|
|
char *buf;
|
|
|
|
|
|
2011-04-07 00:11:01 -04:00
|
|
|
cfgId = get_ts_config_oid(stringToQualifiedNameList(*newval), true);
|
2007-08-20 21:11:32 -04:00
|
|
|
|
Accept a non-existent value in "ALTER USER/DATABASE SET ..." command.
When default_text_search_config, default_tablespace, or temp_tablespaces
setting is set per-user or per-database, with an "ALTER USER/DATABASE SET
..." statement, don't throw an error if the text search configuration or
tablespace does not exist. In case of text search configuration, even if
it doesn't exist in the current database, it might exist in another
database, where the setting is intended to have its effect. This behavior
is now the same as search_path's.
Tablespaces are cluster-wide, so the same argument doesn't hold for
tablespaces, but there's a problem with pg_dumpall: it dumps "ALTER USER
SET ..." statements before the "CREATE TABLESPACE" statements. Arguably
that's pg_dumpall's fault - it should dump the statements in such an order
that the tablespace is created first and then the "ALTER USER SET
default_tablespace ..." statements after that - but it seems better to be
consistent with search_path and default_text_search_config anyway. Besides,
you could still create a dump that throws an error, by creating the
tablespace, running "ALTER USER SET default_tablespace", then dropping the
tablespace and running pg_dumpall on that.
Backpatch to all supported versions.
2012-01-30 03:32:46 -05:00
|
|
|
/*
|
2013-09-03 18:56:22 -04:00
|
|
|
* When source == PGC_S_TEST, don't throw a hard error for a
|
|
|
|
|
* nonexistent configuration, only a NOTICE. See comments in guc.h.
|
Accept a non-existent value in "ALTER USER/DATABASE SET ..." command.
When default_text_search_config, default_tablespace, or temp_tablespaces
setting is set per-user or per-database, with an "ALTER USER/DATABASE SET
..." statement, don't throw an error if the text search configuration or
tablespace does not exist. In case of text search configuration, even if
it doesn't exist in the current database, it might exist in another
database, where the setting is intended to have its effect. This behavior
is now the same as search_path's.
Tablespaces are cluster-wide, so the same argument doesn't hold for
tablespaces, but there's a problem with pg_dumpall: it dumps "ALTER USER
SET ..." statements before the "CREATE TABLESPACE" statements. Arguably
that's pg_dumpall's fault - it should dump the statements in such an order
that the tablespace is created first and then the "ALTER USER SET
default_tablespace ..." statements after that - but it seems better to be
consistent with search_path and default_text_search_config anyway. Besides,
you could still create a dump that throws an error, by creating the
tablespace, running "ALTER USER SET default_tablespace", then dropping the
tablespace and running pg_dumpall on that.
Backpatch to all supported versions.
2012-01-30 03:32:46 -05:00
|
|
|
*/
|
2007-08-20 21:11:32 -04:00
|
|
|
if (!OidIsValid(cfgId))
|
Accept a non-existent value in "ALTER USER/DATABASE SET ..." command.
When default_text_search_config, default_tablespace, or temp_tablespaces
setting is set per-user or per-database, with an "ALTER USER/DATABASE SET
..." statement, don't throw an error if the text search configuration or
tablespace does not exist. In case of text search configuration, even if
it doesn't exist in the current database, it might exist in another
database, where the setting is intended to have its effect. This behavior
is now the same as search_path's.
Tablespaces are cluster-wide, so the same argument doesn't hold for
tablespaces, but there's a problem with pg_dumpall: it dumps "ALTER USER
SET ..." statements before the "CREATE TABLESPACE" statements. Arguably
that's pg_dumpall's fault - it should dump the statements in such an order
that the tablespace is created first and then the "ALTER USER SET
default_tablespace ..." statements after that - but it seems better to be
consistent with search_path and default_text_search_config anyway. Besides,
you could still create a dump that throws an error, by creating the
tablespace, running "ALTER USER SET default_tablespace", then dropping the
tablespace and running pg_dumpall on that.
Backpatch to all supported versions.
2012-01-30 03:32:46 -05:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
2007-08-20 21:11:32 -04:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Modify the actually stored value to be fully qualified, to ensure
|
|
|
|
|
* later changes of search_path don't affect it.
|
|
|
|
|
*/
|
2010-02-14 13:42:19 -05:00
|
|
|
tuple = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
|
2007-08-20 21:11:32 -04:00
|
|
|
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 */
|
2011-04-07 00:11:01 -04:00
|
|
|
free(*newval);
|
|
|
|
|
*newval = strdup(buf);
|
2007-08-20 21:11:32 -04:00
|
|
|
pfree(buf);
|
2013-01-20 23:09:35 -05:00
|
|
|
if (!*newval)
|
2011-04-07 00:11:01 -04:00
|
|
|
return false;
|
2007-08-20 21:11:32 -04:00
|
|
|
}
|
|
|
|
|
|
2011-04-07 00:11:01 -04:00
|
|
|
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;
|
2007-08-20 21:11:32 -04:00
|
|
|
}
|