mirror of https://github.com/postgres/postgres
datatype by array_eq and array_cmp; use this to solve problems with memory leaks in array indexing support. The parser's equality_oper and ordering_oper routines also use the cache. Change the operator search algorithms to look for appropriate btree or hash index opclasses, instead of assuming operators named '<' or '=' have the right semantics. (ORDER BY ASC/DESC now also look at opclasses, instead of assuming '<' and '>' are the right things.) Add several more index opclasses so that there is no regression in functionality for base datatypes. initdb forced due to catalog additions.REL7_4_STABLE
parent
d89578ccbe
commit
ec646dbc65
@ -0,0 +1,292 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* typcache.c |
||||
* POSTGRES type cache code |
||||
* |
||||
* The type cache exists to speed lookup of certain information about data |
||||
* types that is not directly available from a type's pg_type row. In |
||||
* particular, we use a type's default btree opclass, or the default hash |
||||
* opclass if no btree opclass exists, to determine which operators should |
||||
* be used for grouping and sorting the type (GROUP BY, ORDER BY ASC/DESC). |
||||
* |
||||
* Several seemingly-odd choices have been made to support use of the type |
||||
* cache by the generic array comparison routines array_eq() and array_cmp(). |
||||
* Because these routines are used as index support operations, they cannot |
||||
* leak memory. To allow them to execute efficiently, all information that |
||||
* either of them would like to re-use across calls is made available in the |
||||
* type cache. |
||||
* |
||||
* Once created, a type cache entry lives as long as the backend does, so |
||||
* there is no need for a call to release a cache entry. (For present uses, |
||||
* it would be okay to flush type cache entries at the ends of transactions, |
||||
* if we needed to reclaim space.) |
||||
* |
||||
* There is presently no provision for clearing out a cache entry if the |
||||
* stored data becomes obsolete. (The code will work if a type acquires |
||||
* opclasses it didn't have before while a backend runs --- but not if the |
||||
* definition of an existing opclass is altered.) However, the relcache |
||||
* doesn't cope with opclasses changing under it, either, so this seems |
||||
* a low-priority problem. |
||||
* |
||||
* |
||||
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* IDENTIFICATION |
||||
* $Header: /cvsroot/pgsql/src/backend/utils/cache/typcache.c,v 1.1 2003/08/17 19:58:06 tgl Exp $ |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "access/genam.h" |
||||
#include "access/heapam.h" |
||||
#include "access/hash.h" |
||||
#include "access/nbtree.h" |
||||
#include "catalog/catname.h" |
||||
#include "catalog/indexing.h" |
||||
#include "catalog/pg_am.h" |
||||
#include "catalog/pg_opclass.h" |
||||
#include "parser/parse_coerce.h" |
||||
#include "utils/builtins.h" |
||||
#include "utils/catcache.h" |
||||
#include "utils/fmgroids.h" |
||||
#include "utils/hsearch.h" |
||||
#include "utils/lsyscache.h" |
||||
#include "utils/typcache.h" |
||||
|
||||
|
||||
static HTAB *TypeCacheHash = NULL; |
||||
|
||||
|
||||
static Oid lookup_default_opclass(Oid type_id, Oid am_id); |
||||
|
||||
|
||||
/*
|
||||
* lookup_type_cache |
||||
* |
||||
* Fetch the type cache entry for the specified datatype, and make sure that |
||||
* all the fields requested by bits in 'flags' are valid. |
||||
* |
||||
* The result is never NULL --- we will elog() if the passed type OID is |
||||
* invalid. Note however that we may fail to find one or more of the |
||||
* requested opclass-dependent fields; the caller needs to check whether |
||||
* the fields are InvalidOid or not. |
||||
*/ |
||||
TypeCacheEntry * |
||||
lookup_type_cache(Oid type_id, int flags) |
||||
{ |
||||
TypeCacheEntry *typentry; |
||||
bool found; |
||||
|
||||
if (TypeCacheHash == NULL) |
||||
{ |
||||
/* First time through: initialize the hash table */ |
||||
HASHCTL ctl; |
||||
|
||||
if (!CacheMemoryContext) |
||||
CreateCacheMemoryContext(); |
||||
|
||||
MemSet(&ctl, 0, sizeof(ctl)); |
||||
ctl.keysize = sizeof(Oid); |
||||
ctl.entrysize = sizeof(TypeCacheEntry); |
||||
ctl.hash = tag_hash; |
||||
TypeCacheHash = hash_create("Type information cache", 64, |
||||
&ctl, HASH_ELEM | HASH_FUNCTION); |
||||
} |
||||
|
||||
/* Try to look up an existing entry */ |
||||
typentry = (TypeCacheEntry *) hash_search(TypeCacheHash, |
||||
(void *) &type_id, |
||||
HASH_FIND, NULL); |
||||
if (typentry == NULL) |
||||
{ |
||||
/*
|
||||
* If we didn't find one, we want to make one. But first get the |
||||
* required info from the pg_type row, just to make sure we don't |
||||
* make a cache entry for an invalid type OID. |
||||
*/ |
||||
int16 typlen; |
||||
bool typbyval; |
||||
char typalign; |
||||
|
||||
get_typlenbyvalalign(type_id, &typlen, &typbyval, &typalign); |
||||
|
||||
typentry = (TypeCacheEntry *) hash_search(TypeCacheHash, |
||||
(void *) &type_id, |
||||
HASH_ENTER, &found); |
||||
if (typentry == NULL) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_OUT_OF_MEMORY), |
||||
errmsg("out of memory"))); |
||||
Assert(!found); /* it wasn't there a moment ago */ |
||||
|
||||
MemSet(typentry, 0, sizeof(TypeCacheEntry)); |
||||
typentry->type_id = type_id; |
||||
typentry->typlen = typlen; |
||||
typentry->typbyval = typbyval; |
||||
typentry->typalign = typalign; |
||||
} |
||||
|
||||
/* If we haven't already found the opclass, try to do so */ |
||||
if (flags != 0 && typentry->btree_opc == InvalidOid) |
||||
{ |
||||
typentry->btree_opc = lookup_default_opclass(type_id, |
||||
BTREE_AM_OID); |
||||
/* Only care about hash opclass if no btree opclass... */ |
||||
if (typentry->btree_opc == InvalidOid) |
||||
{ |
||||
if (typentry->hash_opc == InvalidOid) |
||||
typentry->hash_opc = lookup_default_opclass(type_id, |
||||
HASH_AM_OID); |
||||
} |
||||
else |
||||
{ |
||||
/*
|
||||
* If we find a btree opclass where previously we only found |
||||
* a hash opclass, forget the hash equality operator so we |
||||
* can use the btree operator instead. |
||||
*/ |
||||
typentry->eq_opr = InvalidOid; |
||||
typentry->eq_opr_finfo.fn_oid = InvalidOid; |
||||
} |
||||
} |
||||
|
||||
/* Look for requested operators and functions */ |
||||
if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO)) && |
||||
typentry->eq_opr == InvalidOid) |
||||
{ |
||||
if (typentry->btree_opc != InvalidOid) |
||||
typentry->eq_opr = get_opclass_member(typentry->btree_opc, |
||||
BTEqualStrategyNumber); |
||||
if (typentry->eq_opr == InvalidOid && |
||||
typentry->hash_opc != InvalidOid) |
||||
typentry->eq_opr = get_opclass_member(typentry->hash_opc, |
||||
HTEqualStrategyNumber); |
||||
} |
||||
if ((flags & TYPECACHE_LT_OPR) && typentry->lt_opr == InvalidOid) |
||||
{ |
||||
if (typentry->btree_opc != InvalidOid) |
||||
typentry->lt_opr = get_opclass_member(typentry->btree_opc, |
||||
BTLessStrategyNumber); |
||||
} |
||||
if ((flags & TYPECACHE_GT_OPR) && typentry->gt_opr == InvalidOid) |
||||
{ |
||||
if (typentry->btree_opc != InvalidOid) |
||||
typentry->gt_opr = get_opclass_member(typentry->btree_opc, |
||||
BTGreaterStrategyNumber); |
||||
} |
||||
if ((flags & (TYPECACHE_CMP_PROC | TYPECACHE_CMP_PROC_FINFO)) && |
||||
typentry->cmp_proc == InvalidOid) |
||||
{ |
||||
if (typentry->btree_opc != InvalidOid) |
||||
typentry->cmp_proc = get_opclass_proc(typentry->btree_opc, |
||||
BTORDER_PROC); |
||||
} |
||||
|
||||
/*
|
||||
* Set up fmgr lookup info as requested |
||||
* |
||||
* Note: we tell fmgr the finfo structures live in CacheMemoryContext, |
||||
* which is not quite right (they're really in DynaHashContext) but this |
||||
* will do for our purposes. |
||||
*/ |
||||
if ((flags & TYPECACHE_EQ_OPR_FINFO) && |
||||
typentry->eq_opr_finfo.fn_oid == InvalidOid && |
||||
typentry->eq_opr != InvalidOid) |
||||
{ |
||||
Oid eq_opr_func; |
||||
|
||||
eq_opr_func = get_opcode(typentry->eq_opr); |
||||
if (eq_opr_func != InvalidOid) |
||||
fmgr_info_cxt(eq_opr_func, &typentry->eq_opr_finfo, |
||||
CacheMemoryContext); |
||||
} |
||||
if ((flags & TYPECACHE_CMP_PROC_FINFO) && |
||||
typentry->cmp_proc_finfo.fn_oid == InvalidOid && |
||||
typentry->cmp_proc != InvalidOid) |
||||
{ |
||||
fmgr_info_cxt(typentry->cmp_proc, &typentry->cmp_proc_finfo, |
||||
CacheMemoryContext); |
||||
} |
||||
|
||||
return typentry; |
||||
} |
||||
|
||||
/*
|
||||
* lookup_default_opclass |
||||
* |
||||
* Given the OIDs of a datatype and an access method, find the default |
||||
* operator class, if any. Returns InvalidOid if there is none. |
||||
*/ |
||||
static Oid |
||||
lookup_default_opclass(Oid type_id, Oid am_id) |
||||
{ |
||||
int nexact = 0; |
||||
int ncompatible = 0; |
||||
Oid exactOid = InvalidOid; |
||||
Oid compatibleOid = InvalidOid; |
||||
Relation rel; |
||||
ScanKeyData skey[1]; |
||||
SysScanDesc scan; |
||||
HeapTuple tup; |
||||
|
||||
/* If it's a domain, look at the base type instead */ |
||||
type_id = getBaseType(type_id); |
||||
|
||||
/*
|
||||
* We scan through all the opclasses available for the access method, |
||||
* looking for one that is marked default and matches the target type |
||||
* (either exactly or binary-compatibly, but prefer an exact match). |
||||
* |
||||
* We could find more than one binary-compatible match, in which case we |
||||
* require the user to specify which one he wants. If we find more |
||||
* than one exact match, then someone put bogus entries in pg_opclass. |
||||
* |
||||
* This is the same logic as GetDefaultOpClass() in indexcmds.c, except |
||||
* that we consider all opclasses, regardless of the current search path. |
||||
*/ |
||||
rel = heap_openr(OperatorClassRelationName, AccessShareLock); |
||||
|
||||
ScanKeyEntryInitialize(&skey[0], 0x0, |
||||
Anum_pg_opclass_opcamid, F_OIDEQ, |
||||
ObjectIdGetDatum(am_id)); |
||||
|
||||
scan = systable_beginscan(rel, OpclassAmNameNspIndex, true, |
||||
SnapshotNow, 1, skey); |
||||
|
||||
while (HeapTupleIsValid(tup = systable_getnext(scan))) |
||||
{ |
||||
Form_pg_opclass opclass = (Form_pg_opclass) GETSTRUCT(tup); |
||||
|
||||
if (opclass->opcdefault) |
||||
{ |
||||
if (opclass->opcintype == type_id) |
||||
{ |
||||
nexact++; |
||||
exactOid = HeapTupleGetOid(tup); |
||||
} |
||||
else if (IsBinaryCoercible(type_id, opclass->opcintype)) |
||||
{ |
||||
ncompatible++; |
||||
compatibleOid = HeapTupleGetOid(tup); |
||||
} |
||||
} |
||||
} |
||||
|
||||
systable_endscan(scan); |
||||
|
||||
heap_close(rel, AccessShareLock); |
||||
|
||||
if (nexact == 1) |
||||
return exactOid; |
||||
if (nexact != 0) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_DUPLICATE_OBJECT), |
||||
errmsg("there are multiple default operator classes for data type %s", |
||||
format_type_be(type_id)))); |
||||
if (ncompatible == 1) |
||||
return compatibleOid; |
||||
|
||||
return InvalidOid; |
||||
} |
@ -0,0 +1,66 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* typcache.h |
||||
* Type cache definitions. |
||||
* |
||||
* The type cache exists to speed lookup of certain information about data |
||||
* types that is not directly available from a type's pg_type row. |
||||
* |
||||
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* $Id: typcache.h,v 1.1 2003/08/17 19:58:06 tgl Exp $ |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#ifndef TYPCACHE_H |
||||
#define TYPCACHE_H |
||||
|
||||
#include "fmgr.h" |
||||
|
||||
|
||||
typedef struct TypeCacheEntry |
||||
{ |
||||
/* typeId is the hash lookup key and MUST BE FIRST */ |
||||
Oid type_id; /* OID of the data type */ |
||||
|
||||
/* some subsidiary information copied from the pg_type row */ |
||||
int16 typlen; |
||||
bool typbyval; |
||||
char typalign; |
||||
|
||||
/*
|
||||
* Information obtained from opclass entries |
||||
* |
||||
* These will be InvalidOid if no match could be found, or if the |
||||
* information hasn't yet been requested. |
||||
*/ |
||||
Oid btree_opc; /* OID of the default btree opclass */ |
||||
Oid hash_opc; /* OID of the default hash opclass */ |
||||
Oid eq_opr; /* OID of the equality operator */ |
||||
Oid lt_opr; /* OID of the less-than operator */ |
||||
Oid gt_opr; /* OID of the greater-than operator */ |
||||
Oid cmp_proc; /* OID of the btree comparison function */ |
||||
|
||||
/*
|
||||
* Pre-set-up fmgr call info for the equality operator and the btree |
||||
* comparison function. These are kept in the type cache to avoid |
||||
* problems with memory leaks in repeated calls to array_eq and array_cmp. |
||||
* There is not currently a need to maintain call info for the lt_opr |
||||
* or gt_opr. |
||||
*/ |
||||
FmgrInfo eq_opr_finfo; |
||||
FmgrInfo cmp_proc_finfo; |
||||
} TypeCacheEntry; |
||||
|
||||
/* Bit flags to indicate which fields a given caller needs to have set */ |
||||
#define TYPECACHE_EQ_OPR 0x0001 |
||||
#define TYPECACHE_LT_OPR 0x0002 |
||||
#define TYPECACHE_GT_OPR 0x0004 |
||||
#define TYPECACHE_CMP_PROC 0x0008 |
||||
#define TYPECACHE_EQ_OPR_FINFO 0x0010 |
||||
#define TYPECACHE_CMP_PROC_FINFO 0x0020 |
||||
|
||||
extern TypeCacheEntry *lookup_type_cache(Oid type_id, int flags); |
||||
|
||||
#endif /* TYPCACHE_H */ |
Loading…
Reference in new issue