|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* plancat.c
|
|
|
|
* routines for accessing the system catalogs
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
|
|
|
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/plancat.c,v 1.44 2000/01/15 02:59:31 petere Exp $
|
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
#include "access/genam.h"
|
|
|
|
#include "access/heapam.h"
|
|
|
|
#include "catalog/catname.h"
|
|
|
|
#include "catalog/pg_amop.h"
|
|
|
|
#include "catalog/pg_inherits.h"
|
|
|
|
#include "optimizer/clauses.h"
|
|
|
|
#include "optimizer/internal.h"
|
|
|
|
#include "optimizer/paths.h"
|
|
|
|
#include "optimizer/plancat.h"
|
|
|
|
#include "parser/parsetree.h"
|
|
|
|
#include "utils/syscache.h"
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* relation_info -
|
|
|
|
* Retrieves catalog information for a given relation.
|
|
|
|
* Given the rangetable index of the relation, return the following info:
|
|
|
|
* whether the relation has secondary indices
|
|
|
|
* number of pages
|
|
|
|
* number of tuples
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
relation_info(Query *root, Index relid,
|
|
|
|
bool *hasindex, long *pages, double *tuples)
|
|
|
|
{
|
|
|
|
Oid relationObjectId = getrelid(relid, root->rtable);
|
|
|
|
HeapTuple relationTuple;
|
|
|
|
Form_pg_class relation;
|
|
|
|
|
|
|
|
relationTuple = SearchSysCacheTuple(RELOID,
|
|
|
|
ObjectIdGetDatum(relationObjectId),
|
|
|
|
0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(relationTuple))
|
|
|
|
elog(ERROR, "relation_info: Relation %u not found",
|
|
|
|
relationObjectId);
|
|
|
|
relation = (Form_pg_class) GETSTRUCT(relationTuple);
|
|
|
|
|
|
|
|
*hasindex = (relation->relhasindex) ? true : false;
|
|
|
|
*pages = relation->relpages;
|
|
|
|
*tuples = relation->reltuples;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* find_secondary_indexes
|
|
|
|
* Creates a list of IndexOptInfo nodes containing information for each
|
|
|
|
* secondary index defined on the given relation.
|
|
|
|
*
|
|
|
|
* 'relid' is the RT index of the relation for which indices are being located
|
|
|
|
*
|
|
|
|
* Returns a list of new IndexOptInfo nodes.
|
|
|
|
*/
|
|
|
|
List *
|
|
|
|
find_secondary_indexes(Query *root, Index relid)
|
|
|
|
{
|
|
|
|
List *indexes = NIL;
|
|
|
|
Oid indrelid = getrelid(relid, root->rtable);
|
|
|
|
Relation relation;
|
|
|
|
HeapScanDesc scan;
|
|
|
|
ScanKeyData indexKey;
|
|
|
|
HeapTuple indexTuple;
|
|
|
|
|
|
|
|
/* Scan pg_index for tuples describing indexes of this rel */
|
|
|
|
relation = heap_openr(IndexRelationName, AccessShareLock);
|
|
|
|
|
|
|
|
ScanKeyEntryInitialize(&indexKey, 0,
|
|
|
|
Anum_pg_index_indrelid,
|
|
|
|
F_OIDEQ,
|
|
|
|
ObjectIdGetDatum(indrelid));
|
|
|
|
|
|
|
|
scan = heap_beginscan(relation, 0, SnapshotNow,
|
|
|
|
1, &indexKey);
|
|
|
|
|
|
|
|
while (HeapTupleIsValid(indexTuple = heap_getnext(scan, 0)))
|
|
|
|
{
|
|
|
|
Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
|
|
|
|
IndexOptInfo *info = makeNode(IndexOptInfo);
|
|
|
|
int i;
|
|
|
|
Relation indexRelation;
|
|
|
|
uint16 amstrategy;
|
|
|
|
Oid relam;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Need to make these arrays large enough to be sure there is a
|
|
|
|
* terminating 0 at the end of each one.
|
|
|
|
*/
|
|
|
|
info->classlist = (Oid *) palloc(sizeof(Oid) * (INDEX_MAX_KEYS+1));
|
|
|
|
info->indexkeys = (int *) palloc(sizeof(int) * (INDEX_MAX_KEYS+1));
|
|
|
|
info->ordering = (Oid *) palloc(sizeof(Oid) * (INDEX_MAX_KEYS+1));
|
|
|
|
|
|
|
|
/* Extract info from the pg_index tuple */
|
|
|
|
info->indexoid = index->indexrelid;
|
|
|
|
info->indproc = index->indproc; /* functional index ?? */
|
|
|
|
if (VARSIZE(&index->indpred) != 0) /* partial index ?? */
|
|
|
|
{
|
|
|
|
char *predString = fmgr(F_TEXTOUT, &index->indpred);
|
|
|
|
info->indpred = (List *) stringToNode(predString);
|
|
|
|
pfree(predString);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
info->indpred = NIL;
|
|
|
|
|
|
|
|
for (i = 0; i < INDEX_MAX_KEYS; i++)
|
|
|
|
info->indexkeys[i] = index->indkey[i];
|
|
|
|
info->indexkeys[INDEX_MAX_KEYS] = 0;
|
|
|
|
for (i = 0; i < INDEX_MAX_KEYS; i++)
|
|
|
|
info->classlist[i] = index->indclass[i];
|
|
|
|
info->classlist[INDEX_MAX_KEYS] = (Oid) 0;
|
|
|
|
|
|
|
|
/* Extract info from the relation descriptor for the index */
|
|
|
|
indexRelation = index_open(index->indexrelid);
|
|
|
|
#ifdef notdef
|
|
|
|
/* XXX should iterate through strategies -- but how? use #1 for now */
|
|
|
|
amstrategy = indexRelation->rd_am->amstrategies;
|
|
|
|
#endif /* notdef */
|
|
|
|
amstrategy = 1;
|
|
|
|
relam = indexRelation->rd_rel->relam;
|
|
|
|
info->relam = relam;
|
|
|
|
info->pages = indexRelation->rd_rel->relpages;
|
|
|
|
info->tuples = indexRelation->rd_rel->reltuples;
|
|
|
|
index_close(indexRelation);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Fetch the ordering operators associated with the index.
|
|
|
|
*
|
|
|
|
* XXX what if it's a hash or other unordered index?
|
|
|
|
*/
|
|
|
|
MemSet(info->ordering, 0, sizeof(Oid) * (INDEX_MAX_KEYS+1));
|
|
|
|
for (i = 0; i < INDEX_MAX_KEYS && index->indclass[i]; i++)
|
|
|
|
{
|
|
|
|
HeapTuple amopTuple;
|
|
|
|
|
|
|
|
amopTuple = SearchSysCacheTuple(AMOPSTRATEGY,
|
|
|
|
ObjectIdGetDatum(relam),
|
|
|
|
ObjectIdGetDatum(index->indclass[i]),
|
|
|
|
UInt16GetDatum(amstrategy),
|
|
|
|
0);
|
|
|
|
if (!HeapTupleIsValid(amopTuple))
|
|
|
|
elog(ERROR, "find_secondary_indexes: no amop %u %u %d",
|
|
|
|
relam, index->indclass[i], amstrategy);
|
|
|
|
info->ordering[i] = ((Form_pg_amop) GETSTRUCT(amopTuple))->amopopr;
|
|
|
|
}
|
|
|
|
|
|
|
|
indexes = lcons(info, indexes);
|
|
|
|
}
|
|
|
|
|
|
|
|
heap_endscan(scan);
|
|
|
|
heap_close(relation, AccessShareLock);
|
|
|
|
|
|
|
|
return indexes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* index_selectivity
|
|
|
|
* Estimate the selectivity of an index scan with the given index quals.
|
|
|
|
*
|
|
|
|
* NOTE: an indexscan plan node can actually represent several passes,
|
|
|
|
* but here we consider the cost of just one pass.
|
|
|
|
*
|
|
|
|
* 'root' is the query root
|
|
|
|
* 'rel' is the relation being scanned
|
|
|
|
* 'index' is the index to be used
|
|
|
|
* 'indexquals' is the list of qual condition exprs (implicit AND semantics)
|
|
|
|
* '*idxPages' receives an estimate of the number of index pages touched
|
|
|
|
* '*idxSelec' receives an estimate of selectivity of the scan, ie fraction
|
|
|
|
* of the relation's tuples that will be retrieved
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
index_selectivity(Query *root,
|
|
|
|
RelOptInfo *rel,
|
|
|
|
IndexOptInfo *index,
|
|
|
|
List *indexquals,
|
|
|
|
long *idxPages,
|
|
|
|
Selectivity *idxSelec)
|
|
|
|
{
|
|
|
|
int relid;
|
|
|
|
Oid baserelid,
|
|
|
|
indexrelid;
|
|
|
|
HeapTuple indRel,
|
|
|
|
indexTuple;
|
|
|
|
Form_pg_class indexrelation;
|
|
|
|
Oid relam;
|
|
|
|
Form_pg_index pgindex;
|
|
|
|
int nIndexKeys;
|
|
|
|
float64data npages,
|
|
|
|
select,
|
|
|
|
fattr_select;
|
|
|
|
bool nphack = false;
|
|
|
|
List *q;
|
|
|
|
|
|
|
|
Assert(length(rel->relids) == 1); /* must be a base rel */
|
|
|
|
relid = lfirsti(rel->relids);
|
|
|
|
|
|
|
|
baserelid = getrelid(relid, root->rtable);
|
|
|
|
indexrelid = index->indexoid;
|
|
|
|
|
|
|
|
indRel = SearchSysCacheTuple(RELOID,
|
|
|
|
ObjectIdGetDatum(indexrelid),
|
|
|
|
0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(indRel))
|
|
|
|
elog(ERROR, "index_selectivity: index %u not found in pg_class",
|
|
|
|
indexrelid);
|
|
|
|
indexrelation = (Form_pg_class) GETSTRUCT(indRel);
|
|
|
|
relam = indexrelation->relam;
|
|
|
|
|
|
|
|
indexTuple = SearchSysCacheTuple(INDEXRELID,
|
|
|
|
ObjectIdGetDatum(indexrelid),
|
|
|
|
0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(indexTuple))
|
|
|
|
elog(ERROR, "index_selectivity: index %u not found in pg_index",
|
|
|
|
indexrelid);
|
|
|
|
pgindex = (Form_pg_index) GETSTRUCT(indexTuple);
|
|
|
|
|
|
|
|
nIndexKeys = 1;
|
|
|
|
while (pgindex->indclass[nIndexKeys] != InvalidOid)
|
|
|
|
nIndexKeys++;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Hack for non-functional btree npages estimation: npages =
|
|
|
|
* index_pages * selectivity_of_1st_attr_clause(s) - vadim 04/24/97
|
|
|
|
*/
|
|
|
|
if (relam == BTREE_AM_OID && pgindex->indproc == InvalidOid)
|
|
|
|
nphack = true;
|
|
|
|
|
|
|
|
npages = 0.0;
|
|
|
|
select = 1.0;
|
|
|
|
fattr_select = 1.0;
|
|
|
|
|
|
|
|
foreach(q, indexquals)
|
|
|
|
{
|
|
|
|
Node *expr = (Node *) lfirst(q);
|
|
|
|
Oid opno;
|
|
|
|
int dummyrelid;
|
|
|
|
AttrNumber attno;
|
|
|
|
Datum value;
|
|
|
|
int flag;
|
|
|
|
Oid indclass;
|
|
|
|
HeapTuple amopTuple;
|
|
|
|
Form_pg_amop amop;
|
|
|
|
float64 amopnpages,
|
|
|
|
amopselect;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Extract info from clause.
|
|
|
|
*/
|
|
|
|
if (is_opclause(expr))
|
|
|
|
opno = ((Oper *) ((Expr *) expr)->oper)->opno;
|
|
|
|
else
|
|
|
|
opno = InvalidOid;
|
|
|
|
get_relattval(expr, relid, &dummyrelid, &attno, &value, &flag);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find the AM class for this key.
|
|
|
|
*/
|
|
|
|
if (pgindex->indproc != InvalidOid)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Functional index: AM class is the first one defined since
|
|
|
|
* functional indices have exactly one key.
|
|
|
|
*/
|
|
|
|
indclass = pgindex->indclass[0];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
indclass = InvalidOid;
|
|
|
|
for (i = 0; pgindex->indkey[i]; i++)
|
|
|
|
{
|
|
|
|
if (attno == pgindex->indkey[i])
|
|
|
|
{
|
|
|
|
indclass = pgindex->indclass[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!OidIsValid(indclass))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Presumably this means that we are using a functional index
|
|
|
|
* clause and so had no variable to match to the index key ...
|
|
|
|
* if not we are in trouble.
|
|
|
|
*/
|
|
|
|
elog(NOTICE, "index_selectivity: no key %d in index %u",
|
|
|
|
attno, indexrelid);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
amopTuple = SearchSysCacheTuple(AMOPOPID,
|
|
|
|
ObjectIdGetDatum(indclass),
|
|
|
|
ObjectIdGetDatum(opno),
|
|
|
|
ObjectIdGetDatum(relam),
|
|
|
|
0);
|
|
|
|
if (!HeapTupleIsValid(amopTuple))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* We might get here because indxpath.c selected a binary-
|
|
|
|
* compatible index. Try again with the compatible operator.
|
|
|
|
*/
|
|
|
|
if (opno != InvalidOid)
|
|
|
|
{
|
|
|
|
opno = indexable_operator((Expr *) expr, indclass, relam,
|
|
|
|
((flag & SEL_RIGHT) != 0));
|
|
|
|
amopTuple = SearchSysCacheTuple(AMOPOPID,
|
|
|
|
ObjectIdGetDatum(indclass),
|
|
|
|
ObjectIdGetDatum(opno),
|
|
|
|
ObjectIdGetDatum(relam),
|
|
|
|
0);
|
|
|
|
}
|
|
|
|
if (!HeapTupleIsValid(amopTuple))
|
|
|
|
elog(ERROR, "index_selectivity: no amop %u %u %u",
|
|
|
|
indclass, opno, relam);
|
|
|
|
}
|
|
|
|
amop = (Form_pg_amop) GETSTRUCT(amopTuple);
|
|
|
|
|
|
|
|
if (!nphack)
|
|
|
|
{
|
|
|
|
amopnpages = (float64) fmgr(amop->amopnpages,
|
|
|
|
(char *) opno,
|
|
|
|
(char *) baserelid,
|
|
|
|
(char *) (int) attno,
|
|
|
|
(char *) value,
|
|
|
|
(char *) flag,
|
|
|
|
(char *) nIndexKeys,
|
|
|
|
(char *) indexrelid);
|
|
|
|
if (PointerIsValid(amopnpages))
|
|
|
|
npages += *amopnpages;
|
|
|
|
}
|
|
|
|
|
|
|
|
amopselect = (float64) fmgr(amop->amopselect,
|
|
|
|
(char *) opno,
|
|
|
|
(char *) baserelid,
|
|
|
|
(char *) (int) attno,
|
|
|
|
(char *) value,
|
|
|
|
(char *) flag,
|
|
|
|
(char *) nIndexKeys,
|
|
|
|
(char *) indexrelid);
|
|
|
|
if (PointerIsValid(amopselect))
|
|
|
|
{
|
|
|
|
select *= *amopselect;
|
|
|
|
if (nphack && attno == pgindex->indkey[0])
|
|
|
|
fattr_select *= *amopselect;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Estimation of npages below is hack of course, but it's better than
|
|
|
|
* it was before. - vadim 04/09/97
|
|
|
|
*/
|
|
|
|
if (nphack)
|
|
|
|
{
|
|
|
|
npages = fattr_select * indexrelation->relpages;
|
|
|
|
*idxPages = (long) ceil((double) npages);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (nIndexKeys > 1)
|
|
|
|
npages = npages / (1.0 + nIndexKeys);
|
|
|
|
*idxPages = (long) ceil((double) (npages / nIndexKeys));
|
|
|
|
}
|
|
|
|
*idxSelec = select;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* restriction_selectivity
|
|
|
|
*
|
|
|
|
* Returns the selectivity of a specified operator.
|
|
|
|
* This code executes registered procedures stored in the
|
|
|
|
* operator relation, by calling the function manager.
|
|
|
|
*
|
|
|
|
* XXX The assumption in the selectivity procedures is that if the
|
|
|
|
* relation OIDs or attribute numbers are 0, then the clause
|
|
|
|
* isn't of the form (op var const).
|
|
|
|
*/
|
|
|
|
Selectivity
|
|
|
|
restriction_selectivity(Oid functionObjectId,
|
|
|
|
Oid operatorObjectId,
|
|
|
|
Oid relationObjectId,
|
|
|
|
AttrNumber attributeNumber,
|
|
|
|
Datum constValue,
|
|
|
|
int constFlag)
|
|
|
|
{
|
|
|
|
float64 result;
|
|
|
|
|
|
|
|
result = (float64) fmgr(functionObjectId,
|
|
|
|
(char *) operatorObjectId,
|
|
|
|
(char *) relationObjectId,
|
|
|
|
(char *) (int) attributeNumber,
|
|
|
|
(char *) constValue,
|
|
|
|
(char *) constFlag,
|
|
|
|
NULL);
|
|
|
|
if (!PointerIsValid(result))
|
|
|
|
elog(ERROR, "restriction_selectivity: bad pointer");
|
|
|
|
|
|
|
|
if (*result < 0.0 || *result > 1.0)
|
|
|
|
elog(ERROR, "restriction_selectivity: bad value %f", *result);
|
|
|
|
|
|
|
|
return (Selectivity) *result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* join_selectivity
|
|
|
|
*
|
|
|
|
* Returns the selectivity of an operator, given the join clause
|
|
|
|
* information.
|
|
|
|
*
|
|
|
|
* XXX The assumption in the selectivity procedures is that if the
|
|
|
|
* relation OIDs or attribute numbers are 0, then the clause
|
|
|
|
* isn't of the form (op var var).
|
|
|
|
*/
|
|
|
|
Selectivity
|
|
|
|
join_selectivity(Oid functionObjectId,
|
|
|
|
Oid operatorObjectId,
|
|
|
|
Oid relationObjectId1,
|
|
|
|
AttrNumber attributeNumber1,
|
|
|
|
Oid relationObjectId2,
|
|
|
|
AttrNumber attributeNumber2)
|
|
|
|
{
|
|
|
|
float64 result;
|
|
|
|
|
|
|
|
result = (float64) fmgr(functionObjectId,
|
|
|
|
(char *) operatorObjectId,
|
|
|
|
(char *) relationObjectId1,
|
|
|
|
(char *) (int) attributeNumber1,
|
|
|
|
(char *) relationObjectId2,
|
|
|
|
(char *) (int) attributeNumber2,
|
|
|
|
NULL);
|
|
|
|
if (!PointerIsValid(result))
|
|
|
|
elog(ERROR, "join_selectivity: bad pointer");
|
|
|
|
|
|
|
|
if (*result < 0.0 || *result > 1.0)
|
|
|
|
elog(ERROR, "join_selectivity: bad value %f", *result);
|
|
|
|
|
|
|
|
return (Selectivity) *result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* find_inheritance_children
|
|
|
|
*
|
|
|
|
* Returns an integer list containing the OIDs of all relations which
|
|
|
|
* inherit *directly* from the relation with OID 'inhparent'.
|
|
|
|
*/
|
|
|
|
List *
|
|
|
|
find_inheritance_children(Oid inhparent)
|
|
|
|
{
|
|
|
|
static ScanKeyData key[1] = {
|
|
|
|
{0, Anum_pg_inherits_inhparent, F_OIDEQ}
|
|
|
|
};
|
|
|
|
|
|
|
|
HeapTuple inheritsTuple;
|
|
|
|
Relation relation;
|
|
|
|
HeapScanDesc scan;
|
|
|
|
List *list = NIL;
|
|
|
|
Oid inhrelid;
|
|
|
|
|
|
|
|
fmgr_info(F_OIDEQ, &key[0].sk_func);
|
|
|
|
key[0].sk_nargs = key[0].sk_func.fn_nargs;
|
|
|
|
key[0].sk_argument = ObjectIdGetDatum(inhparent);
|
|
|
|
|
|
|
|
relation = heap_openr(InheritsRelationName, AccessShareLock);
|
|
|
|
scan = heap_beginscan(relation, 0, SnapshotNow, 1, key);
|
|
|
|
while (HeapTupleIsValid(inheritsTuple = heap_getnext(scan, 0)))
|
|
|
|
{
|
|
|
|
inhrelid = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhrelid;
|
|
|
|
list = lappendi(list, inhrelid);
|
|
|
|
}
|
|
|
|
heap_endscan(scan);
|
|
|
|
heap_close(relation, AccessShareLock);
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef NOT_USED
|
|
|
|
/*
|
|
|
|
* VersionGetParents
|
|
|
|
*
|
|
|
|
* Returns a LISP list containing the OIDs of all relations which are
|
|
|
|
* base relations of the relation with OID 'verrelid'.
|
|
|
|
*/
|
|
|
|
List *
|
|
|
|
VersionGetParents(Oid verrelid)
|
|
|
|
{
|
|
|
|
static ScanKeyData key[1] = {
|
|
|
|
{0, Anum_pg_version_verrelid, F_OIDEQ}
|
|
|
|
};
|
|
|
|
|
|
|
|
HeapTuple versionTuple;
|
|
|
|
Relation relation;
|
|
|
|
HeapScanDesc scan;
|
|
|
|
Oid verbaseid;
|
|
|
|
List *list = NIL;
|
|
|
|
|
|
|
|
fmgr_info(F_OIDEQ, &key[0].sk_func);
|
|
|
|
key[0].sk_nargs = key[0].sk_func.fn_nargs;
|
|
|
|
key[0].sk_argument = ObjectIdGetDatum(verrelid);
|
|
|
|
relation = heap_openr(VersionRelationName, AccessShareLock);
|
|
|
|
scan = heap_beginscan(relation, 0, SnapshotNow, 1, key);
|
|
|
|
while (HeapTupleIsValid(versionTuple = heap_getnext(scan, 0)))
|
|
|
|
{
|
|
|
|
verbaseid = ((Form_pg_version)
|
|
|
|
GETSTRUCT(versionTuple))->verbaseid;
|
|
|
|
|
|
|
|
list = lconsi(verbaseid, list);
|
|
|
|
|
|
|
|
key[0].sk_argument = ObjectIdGetDatum(verbaseid);
|
|
|
|
heap_rescan(scan, 0, key);
|
|
|
|
}
|
|
|
|
heap_endscan(scan);
|
|
|
|
heap_close(relation, AccessShareLock);
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|