2009-05-12 02:56:05 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* pg_inherits.c
|
|
|
|
* routines to support manipulation of the pg_inherits relation
|
|
|
|
*
|
|
|
|
* Note: currently, this module only contains inquiry functions; the actual
|
|
|
|
* creation and deletion of pg_inherits entries is done in tablecmds.c.
|
|
|
|
* Perhaps someday that code should be moved here, but it'd have to be
|
|
|
|
* disentangled from other stuff such as pg_depend updates.
|
|
|
|
*
|
|
|
|
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2009-06-11 16:49:15 +02:00
|
|
|
* $PostgreSQL: pgsql/src/backend/catalog/pg_inherits.c,v 1.3 2009/06/11 14:48:55 momjian Exp $
|
2009-05-12 02:56:05 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include "access/heapam.h"
|
|
|
|
#include "catalog/pg_class.h"
|
|
|
|
#include "catalog/pg_inherits.h"
|
2009-05-12 05:11:02 +02:00
|
|
|
#include "catalog/pg_inherits_fn.h"
|
2009-05-12 02:56:05 +02:00
|
|
|
#include "parser/parse_type.h"
|
2009-05-12 05:11:02 +02:00
|
|
|
#include "storage/lmgr.h"
|
2009-05-12 02:56:05 +02:00
|
|
|
#include "utils/fmgroids.h"
|
|
|
|
#include "utils/syscache.h"
|
|
|
|
#include "utils/tqual.h"
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* find_inheritance_children
|
|
|
|
*
|
|
|
|
* Returns a list containing the OIDs of all relations which
|
|
|
|
* inherit *directly* from the relation with OID 'parentrelId'.
|
2009-05-12 05:11:02 +02:00
|
|
|
*
|
|
|
|
* The specified lock type is acquired on each child relation (but not on the
|
|
|
|
* given rel; caller should already have locked it). If lockmode is NoLock
|
|
|
|
* then no locks are acquired, but caller must beware of race conditions
|
|
|
|
* against possible DROPs of child relations.
|
2009-05-12 02:56:05 +02:00
|
|
|
*/
|
|
|
|
List *
|
2009-05-12 05:11:02 +02:00
|
|
|
find_inheritance_children(Oid parentrelId, LOCKMODE lockmode)
|
2009-05-12 02:56:05 +02:00
|
|
|
{
|
|
|
|
List *list = NIL;
|
|
|
|
Relation relation;
|
|
|
|
HeapScanDesc scan;
|
|
|
|
ScanKeyData key[1];
|
|
|
|
HeapTuple inheritsTuple;
|
|
|
|
Oid inhrelid;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Can skip the scan if pg_class shows the relation has never had a
|
|
|
|
* subclass.
|
|
|
|
*/
|
|
|
|
if (!has_subclass(parentrelId))
|
|
|
|
return NIL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* XXX might be a good idea to create an index on pg_inherits' inhparent
|
|
|
|
* field, so that we can use an indexscan instead of sequential scan here.
|
|
|
|
* However, in typical databases pg_inherits won't have enough entries to
|
|
|
|
* justify an indexscan...
|
|
|
|
*/
|
|
|
|
relation = heap_open(InheritsRelationId, AccessShareLock);
|
|
|
|
ScanKeyInit(&key[0],
|
|
|
|
Anum_pg_inherits_inhparent,
|
|
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
|
|
ObjectIdGetDatum(parentrelId));
|
|
|
|
scan = heap_beginscan(relation, SnapshotNow, 1, key);
|
2009-05-12 05:11:02 +02:00
|
|
|
|
2009-05-12 02:56:05 +02:00
|
|
|
while ((inheritsTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
|
|
|
|
{
|
|
|
|
inhrelid = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhrelid;
|
2009-05-12 05:11:02 +02:00
|
|
|
|
|
|
|
if (lockmode != NoLock)
|
|
|
|
{
|
|
|
|
/* Get the lock to synchronize against concurrent drop */
|
|
|
|
LockRelationOid(inhrelid, lockmode);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now that we have the lock, double-check to see if the relation
|
2009-06-11 16:49:15 +02:00
|
|
|
* really exists or not. If not, assume it was dropped while we
|
|
|
|
* waited to acquire lock, and ignore it.
|
2009-05-12 05:11:02 +02:00
|
|
|
*/
|
|
|
|
if (!SearchSysCacheExists(RELOID,
|
|
|
|
ObjectIdGetDatum(inhrelid),
|
|
|
|
0, 0, 0))
|
|
|
|
{
|
|
|
|
/* Release useless lock */
|
|
|
|
UnlockRelationOid(inhrelid, lockmode);
|
|
|
|
/* And ignore this relation */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-05-12 02:56:05 +02:00
|
|
|
list = lappend_oid(list, inhrelid);
|
|
|
|
}
|
2009-05-12 05:11:02 +02:00
|
|
|
|
2009-05-12 02:56:05 +02:00
|
|
|
heap_endscan(scan);
|
|
|
|
heap_close(relation, AccessShareLock);
|
2009-05-12 05:11:02 +02:00
|
|
|
|
2009-05-12 02:56:05 +02:00
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* find_all_inheritors -
|
|
|
|
* Returns a list of relation OIDs including the given rel plus
|
|
|
|
* all relations that inherit from it, directly or indirectly.
|
2009-05-12 05:11:02 +02:00
|
|
|
*
|
|
|
|
* The specified lock type is acquired on all child relations (but not on the
|
|
|
|
* given rel; caller should already have locked it). If lockmode is NoLock
|
|
|
|
* then no locks are acquired, but caller must beware of race conditions
|
|
|
|
* against possible DROPs of child relations.
|
2009-05-12 02:56:05 +02:00
|
|
|
*/
|
|
|
|
List *
|
2009-05-12 05:11:02 +02:00
|
|
|
find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
|
2009-05-12 02:56:05 +02:00
|
|
|
{
|
|
|
|
List *rels_list;
|
|
|
|
ListCell *l;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We build a list starting with the given rel and adding all direct and
|
|
|
|
* indirect children. We can use a single list as both the record of
|
|
|
|
* already-found rels and the agenda of rels yet to be scanned for more
|
|
|
|
* children. This is a bit tricky but works because the foreach() macro
|
|
|
|
* doesn't fetch the next list element until the bottom of the loop.
|
|
|
|
*/
|
|
|
|
rels_list = list_make1_oid(parentrelId);
|
|
|
|
|
|
|
|
foreach(l, rels_list)
|
|
|
|
{
|
|
|
|
Oid currentrel = lfirst_oid(l);
|
|
|
|
List *currentchildren;
|
|
|
|
|
|
|
|
/* Get the direct children of this rel */
|
2009-05-12 05:11:02 +02:00
|
|
|
currentchildren = find_inheritance_children(currentrel, lockmode);
|
2009-05-12 02:56:05 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Add to the queue only those children not already seen. This avoids
|
|
|
|
* making duplicate entries in case of multiple inheritance paths from
|
|
|
|
* the same parent. (It'll also keep us from getting into an infinite
|
|
|
|
* loop, though theoretically there can't be any cycles in the
|
|
|
|
* inheritance graph anyway.)
|
|
|
|
*/
|
|
|
|
rels_list = list_concat_unique_oid(rels_list, currentchildren);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rels_list;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* has_subclass - does this relation have any children?
|
|
|
|
*
|
|
|
|
* In the current implementation, has_subclass returns whether a
|
|
|
|
* particular class *might* have a subclass. It will not return the
|
|
|
|
* correct result if a class had a subclass which was later dropped.
|
|
|
|
* This is because relhassubclass in pg_class is not updated when a
|
|
|
|
* subclass is dropped, primarily because of concurrency concerns.
|
|
|
|
*
|
|
|
|
* Currently has_subclass is only used as an efficiency hack to skip
|
|
|
|
* unnecessary inheritance searches, so this is OK.
|
|
|
|
*
|
|
|
|
* Although this doesn't actually touch pg_inherits, it seems reasonable
|
|
|
|
* to keep it here since it's normally used with the other routines here.
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
has_subclass(Oid relationId)
|
|
|
|
{
|
|
|
|
HeapTuple tuple;
|
|
|
|
bool result;
|
|
|
|
|
|
|
|
tuple = SearchSysCache(RELOID,
|
|
|
|
ObjectIdGetDatum(relationId),
|
|
|
|
0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(tuple))
|
|
|
|
elog(ERROR, "cache lookup failed for relation %u", relationId);
|
|
|
|
|
|
|
|
result = ((Form_pg_class) GETSTRUCT(tuple))->relhassubclass;
|
|
|
|
ReleaseSysCache(tuple);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Given two type OIDs, determine whether the first is a complex type
|
|
|
|
* (class type) that inherits from the second.
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId)
|
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
Oid subclassRelid;
|
|
|
|
Oid superclassRelid;
|
|
|
|
Relation inhrel;
|
|
|
|
List *visited,
|
|
|
|
*queue;
|
|
|
|
ListCell *queue_item;
|
|
|
|
|
|
|
|
/* We need to work with the associated relation OIDs */
|
|
|
|
subclassRelid = typeidTypeRelid(subclassTypeId);
|
|
|
|
if (subclassRelid == InvalidOid)
|
|
|
|
return false; /* not a complex type */
|
|
|
|
superclassRelid = typeidTypeRelid(superclassTypeId);
|
|
|
|
if (superclassRelid == InvalidOid)
|
|
|
|
return false; /* not a complex type */
|
|
|
|
|
|
|
|
/* No point in searching if the superclass has no subclasses */
|
|
|
|
if (!has_subclass(superclassRelid))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Begin the search at the relation itself, so add its relid to the queue.
|
|
|
|
*/
|
|
|
|
queue = list_make1_oid(subclassRelid);
|
|
|
|
visited = NIL;
|
|
|
|
|
|
|
|
inhrel = heap_open(InheritsRelationId, AccessShareLock);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Use queue to do a breadth-first traversal of the inheritance graph from
|
|
|
|
* the relid supplied up to the root. Notice that we append to the queue
|
|
|
|
* inside the loop --- this is okay because the foreach() macro doesn't
|
|
|
|
* advance queue_item until the next loop iteration begins.
|
|
|
|
*/
|
|
|
|
foreach(queue_item, queue)
|
|
|
|
{
|
|
|
|
Oid this_relid = lfirst_oid(queue_item);
|
|
|
|
ScanKeyData skey;
|
|
|
|
HeapScanDesc inhscan;
|
|
|
|
HeapTuple inhtup;
|
|
|
|
|
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* If we've seen this relid already, skip it. This avoids extra work
|
|
|
|
* in multiple-inheritance scenarios, and also protects us from an
|
|
|
|
* infinite loop in case there is a cycle in pg_inherits (though
|
|
|
|
* theoretically that shouldn't happen).
|
2009-05-12 02:56:05 +02:00
|
|
|
*/
|
|
|
|
if (list_member_oid(visited, this_relid))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Okay, this is a not-yet-seen relid. Add it to the list of
|
|
|
|
* already-visited OIDs, then find all the types this relid inherits
|
|
|
|
* from and add them to the queue.
|
|
|
|
*/
|
|
|
|
visited = lappend_oid(visited, this_relid);
|
|
|
|
|
|
|
|
ScanKeyInit(&skey,
|
|
|
|
Anum_pg_inherits_inhrelid,
|
|
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
|
|
ObjectIdGetDatum(this_relid));
|
|
|
|
|
|
|
|
inhscan = heap_beginscan(inhrel, SnapshotNow, 1, &skey);
|
|
|
|
|
|
|
|
while ((inhtup = heap_getnext(inhscan, ForwardScanDirection)) != NULL)
|
|
|
|
{
|
|
|
|
Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inhtup);
|
|
|
|
Oid inhparent = inh->inhparent;
|
|
|
|
|
|
|
|
/* If this is the target superclass, we're done */
|
|
|
|
if (inhparent == superclassRelid)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Else add to queue */
|
|
|
|
queue = lappend_oid(queue, inhparent);
|
|
|
|
}
|
|
|
|
|
|
|
|
heap_endscan(inhscan);
|
|
|
|
|
|
|
|
if (result)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* clean up ... */
|
|
|
|
heap_close(inhrel, AccessShareLock);
|
|
|
|
|
|
|
|
list_free(visited);
|
|
|
|
list_free(queue);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|