mirror of
https://github.com/postgres/postgres.git
synced 2025-07-03 20:02:46 +03:00
Create a 'type cache' that keeps track of the data needed for any particular
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.
This commit is contained in:
4
src/backend/utils/cache/Makefile
vendored
4
src/backend/utils/cache/Makefile
vendored
@ -4,7 +4,7 @@
|
||||
# Makefile for utils/cache
|
||||
#
|
||||
# IDENTIFICATION
|
||||
# $Header: /cvsroot/pgsql/src/backend/utils/cache/Makefile,v 1.17 2002/12/13 19:45:56 tgl Exp $
|
||||
# $Header: /cvsroot/pgsql/src/backend/utils/cache/Makefile,v 1.18 2003/08/17 19:58:06 tgl Exp $
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@ -12,7 +12,7 @@ subdir = src/backend/utils/cache
|
||||
top_builddir = ../../../..
|
||||
include $(top_builddir)/src/Makefile.global
|
||||
|
||||
OBJS = catcache.o inval.o relcache.o syscache.o lsyscache.o
|
||||
OBJS = catcache.o inval.o relcache.o syscache.o lsyscache.o typcache.o
|
||||
|
||||
all: SUBSYS.o
|
||||
|
||||
|
53
src/backend/utils/cache/lsyscache.c
vendored
53
src/backend/utils/cache/lsyscache.c
vendored
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.106 2003/08/11 23:04:49 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.107 2003/08/17 19:58:06 tgl Exp $
|
||||
*
|
||||
* NOTES
|
||||
* Eventually, the index information should go through here, too.
|
||||
@ -122,7 +122,6 @@ get_op_hash_function(Oid opno)
|
||||
{
|
||||
CatCList *catlist;
|
||||
int i;
|
||||
HeapTuple tuple;
|
||||
Oid opclass = InvalidOid;
|
||||
|
||||
/*
|
||||
@ -137,10 +136,8 @@ get_op_hash_function(Oid opno)
|
||||
|
||||
for (i = 0; i < catlist->n_members; i++)
|
||||
{
|
||||
Form_pg_amop aform;
|
||||
|
||||
tuple = &catlist->members[i]->tuple;
|
||||
aform = (Form_pg_amop) GETSTRUCT(tuple);
|
||||
HeapTuple tuple = &catlist->members[i]->tuple;
|
||||
Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
|
||||
|
||||
if (aform->amopstrategy == HTEqualStrategyNumber &&
|
||||
opclass_is_hash(aform->amopclaid))
|
||||
@ -155,20 +152,7 @@ get_op_hash_function(Oid opno)
|
||||
if (OidIsValid(opclass))
|
||||
{
|
||||
/* Found a suitable opclass, get its hash support function */
|
||||
tuple = SearchSysCache(AMPROCNUM,
|
||||
ObjectIdGetDatum(opclass),
|
||||
Int16GetDatum(HASHPROC),
|
||||
0, 0);
|
||||
if (HeapTupleIsValid(tuple))
|
||||
{
|
||||
Form_pg_amproc aform = (Form_pg_amproc) GETSTRUCT(tuple);
|
||||
RegProcedure result;
|
||||
|
||||
result = aform->amproc;
|
||||
ReleaseSysCache(tuple);
|
||||
Assert(RegProcedureIsValid(result));
|
||||
return result;
|
||||
}
|
||||
return get_opclass_proc(opclass, HASHPROC);
|
||||
}
|
||||
|
||||
/* Didn't find a match... */
|
||||
@ -176,6 +160,35 @@ get_op_hash_function(Oid opno)
|
||||
}
|
||||
|
||||
|
||||
/* ---------- AMPROC CACHES ---------- */
|
||||
|
||||
/*
|
||||
* get_opclass_proc
|
||||
* Get the OID of the specified support function
|
||||
* for the specified opclass.
|
||||
*
|
||||
* Returns InvalidOid if there is no pg_amproc entry for the given keys.
|
||||
*/
|
||||
Oid
|
||||
get_opclass_proc(Oid opclass, int16 procnum)
|
||||
{
|
||||
HeapTuple tp;
|
||||
Form_pg_amproc amproc_tup;
|
||||
RegProcedure result;
|
||||
|
||||
tp = SearchSysCache(AMPROCNUM,
|
||||
ObjectIdGetDatum(opclass),
|
||||
Int16GetDatum(procnum),
|
||||
0, 0);
|
||||
if (!HeapTupleIsValid(tp))
|
||||
return InvalidOid;
|
||||
amproc_tup = (Form_pg_amproc) GETSTRUCT(tp);
|
||||
result = amproc_tup->amproc;
|
||||
ReleaseSysCache(tp);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/* ---------- ATTRIBUTE CACHES ---------- */
|
||||
|
||||
/*
|
||||
|
292
src/backend/utils/cache/typcache.c
vendored
Normal file
292
src/backend/utils/cache/typcache.c
vendored
Normal file
@ -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;
|
||||
}
|
Reference in New Issue
Block a user