Add code to extract dependencies from an expression tree, and use it

to build dependencies for rules, constraint expressions, and default
expressions.  Repair some problems in the original design of
recursiveDeletion() exposed by more complex dependency sets.  Fix
regression tests that were deleting things in illegal sequences.
This commit is contained in:
Tom Lane 2002-07-16 05:53:34 +00:00
parent 1e07ab78cc
commit 30ec31604d
13 changed files with 771 additions and 143 deletions

View File

@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/catalog/dependency.c,v 1.2 2002/07/15 16:33:31 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/catalog/dependency.c,v 1.3 2002/07/16 05:53:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -34,6 +34,8 @@
#include "commands/trigger.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteRemove.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
@ -51,14 +53,56 @@ typedef enum ObjectClasses
OCLASS_LANGUAGE, /* pg_language */
OCLASS_OPERATOR, /* pg_operator */
OCLASS_REWRITE, /* pg_rewrite */
OCLASS_TRIGGER /* pg_trigger */
OCLASS_TRIGGER, /* pg_trigger */
MAX_OCLASS /* MUST BE LAST */
} ObjectClasses;
/* expansible list of ObjectAddresses */
typedef struct ObjectAddresses
{
ObjectAddress *refs; /* => palloc'd array */
int numrefs; /* current number of references */
int maxrefs; /* current size of palloc'd array */
struct ObjectAddresses *link; /* list link for use in recursion */
} ObjectAddresses;
/* for find_expr_references_walker */
typedef struct
{
ObjectAddresses addrs; /* addresses being accumulated */
List *rtables; /* list of rangetables to resolve Vars */
} find_expr_references_context;
/*
* Because not all system catalogs have predetermined OIDs, we build a table
* mapping between ObjectClasses and OIDs. This is done at most once per
* backend run, to minimize lookup overhead.
*/
static bool object_classes_initialized = false;
static Oid object_classes[MAX_OCLASS];
static bool recursiveDeletion(const ObjectAddress *object,
DropBehavior behavior,
int recursionLevel,
const ObjectAddress *callingObject,
ObjectAddresses *pending,
Relation depRel);
static void doDeletion(const ObjectAddress *object);
static bool find_expr_references_walker(Node *node,
find_expr_references_context *context);
static void eliminate_duplicate_dependencies(ObjectAddresses *addrs);
static int object_address_comparator(const void *a, const void *b);
static void init_object_addresses(ObjectAddresses *addrs);
static void add_object_address(ObjectClasses oclass, Oid objectId, int32 subId,
ObjectAddresses *addrs);
static void add_exact_object_address(const ObjectAddress *object,
ObjectAddresses *addrs);
static void del_object_address(const ObjectAddress *object,
ObjectAddresses *addrs);
static void del_object_address_by_index(int index, ObjectAddresses *addrs);
static void term_object_addresses(ObjectAddresses *addrs);
static void init_object_classes(void);
static ObjectClasses getObjectClass(const ObjectAddress *object);
static char *getObjectDescription(const ObjectAddress *object);
static void getRelationDescription(StringInfo buffer, Oid relid);
@ -93,7 +137,7 @@ performDeletion(const ObjectAddress *object,
*/
depRel = heap_openr(DependRelationName, RowExclusiveLock);
if (!recursiveDeletion(object, behavior, 0, depRel))
if (!recursiveDeletion(object, behavior, NULL, NULL, depRel))
elog(ERROR, "Cannot drop %s because other objects depend on it"
"\n\tUse DROP ... CASCADE to drop the dependent objects too",
objDescription);
@ -107,28 +151,58 @@ performDeletion(const ObjectAddress *object,
/*
* recursiveDeletion: delete a single object for performDeletion.
*
* Returns TRUE if successful, FALSE if not. recursionLevel is 0 at the
* outer level, >0 when deleting a dependent object.
* Returns TRUE if successful, FALSE if not.
*
* callingObject is NULL at the outer level, else identifies the object that
* we recursed from (the reference object that someone else needs to delete).
* pending is a linked list of objects that outer recursion levels want to
* delete. We remove the target object from any outer-level list it may
* appear in.
* depRel is the already-open pg_depend relation.
*
* In RESTRICT mode, we perform all the deletions anyway, but elog a NOTICE
* and return FALSE if we find a restriction violation. performDeletion
* will then abort the transaction to nullify the deletions. We have to
* do it this way to (a) report all the direct and indirect dependencies
* while (b) not going into infinite recursion if there's a cycle.
*
* This is even more complex than one could wish, because it is possible for
* the same pair of objects to be related by both NORMAL and AUTO (or IMPLICIT)
* dependencies. (Since one or both paths might be indirect, it's very hard
* to prevent this; we must cope instead.) If there is an AUTO/IMPLICIT
* deletion path then we should perform the deletion, and not fail because
* of the NORMAL dependency. So, when we hit a NORMAL dependency we don't
* immediately decide we've failed; instead we stick the NORMAL dependent
* object into a list of pending deletions. If we find a legal path to delete
* that object later on, the recursive call will remove it from our pending
* list. After we've exhausted all such possibilities, we remove the
* remaining pending objects anyway, but emit a notice and prepare to return
* FALSE. (We have to do it this way because the dependent objects *must* be
* removed before we can remove the object they depend on.)
*
* Note: in the case where the AUTO path is traversed first, we will never
* see the NORMAL dependency path because of the pg_depend removals done in
* recursive executions of step 1. The pending list is necessary essentially
* just to make the behavior independent of the order in which pg_depend
* entries are visited.
*/
static bool
recursiveDeletion(const ObjectAddress *object,
DropBehavior behavior,
int recursionLevel,
const ObjectAddress *callingObject,
ObjectAddresses *pending,
Relation depRel)
{
bool ok = true;
char *objDescription;
ObjectAddresses mypending;
ScanKeyData key[3];
int nkeys;
SysScanDesc scan;
HeapTuple tup;
ObjectAddress otherObject;
ObjectAddress owningObject;
bool amOwned = false;
/*
* Get object description for possible use in messages. Must do this
@ -136,11 +210,17 @@ recursiveDeletion(const ObjectAddress *object,
*/
objDescription = getObjectDescription(object);
/*
* Initialize list of restricted objects, and set up chain link.
*/
init_object_addresses(&mypending);
mypending.link = pending;
/*
* Step 1: find and remove pg_depend records that link from this
* object to others. We have to do this anyway, and doing it first
* ensures that we avoid infinite recursion in the case of cycles.
* Also, some dependency types require an error here.
* Also, some dependency types require extra processing here.
*
* When dropping a whole object (subId = 0), remove all pg_depend
* records for its sub-objects too.
@ -180,17 +260,48 @@ recursiveDeletion(const ObjectAddress *object,
break;
case DEPENDENCY_INTERNAL:
/*
* Disallow direct DROP of an object that is part of the
* implementation of another object. (We just elog here,
* rather than issuing a notice and continuing, since
* no other dependencies are likely to be interesting.)
* This object is part of the internal implementation
* of another object. We have three cases:
*
* 1. At the outermost recursion level, disallow the DROP.
* (We just elog here, rather than considering this drop
* to be pending, since no other dependencies are likely
* to be interesting.)
*/
if (recursionLevel == 0)
if (callingObject == NULL)
{
char *otherObjDesc = getObjectDescription(&otherObject);
elog(ERROR, "Cannot drop %s because %s requires it"
"\n\tYou may DROP the other object instead",
objDescription,
getObjectDescription(&otherObject));
break;
"\n\tYou may drop %s instead",
objDescription, otherObjDesc, otherObjDesc);
}
/*
* 2. When recursing from the other end of this dependency,
* it's okay to continue with the deletion. This holds when
* recursing from a whole object that includes the nominal
* other end as a component, too.
*/
if (callingObject->classId == otherObject.classId &&
callingObject->objectId == otherObject.objectId &&
(callingObject->objectSubId == otherObject.objectSubId ||
callingObject->objectSubId == 0))
break;
/*
* 3. When recursing from anyplace else, transform this
* deletion request into a delete of the other object.
* (This will be an error condition iff RESTRICT mode.)
* In this case we finish deleting my dependencies except
* for the INTERNAL link, which will be needed to cause
* the owning object to recurse back to me.
*/
if (amOwned) /* shouldn't happen */
elog(ERROR, "recursiveDeletion: multiple INTERNAL dependencies for %s",
objDescription);
owningObject = otherObject;
amOwned = true;
/* "continue" bypasses the simple_heap_delete call below */
continue;
case DEPENDENCY_PIN:
/*
* Should not happen; PIN dependencies should have zeroes
@ -218,6 +329,34 @@ recursiveDeletion(const ObjectAddress *object,
*/
CommandCounterIncrement();
/*
* If we found we are owned by another object, ask it to delete itself
* instead of proceeding.
*/
if (amOwned)
{
if (behavior == DROP_RESTRICT)
{
elog(NOTICE, "%s depends on %s",
getObjectDescription(&owningObject),
objDescription);
ok = false;
}
else
elog(NOTICE, "Drop cascades to %s",
getObjectDescription(&owningObject));
if (!recursiveDeletion(&owningObject, behavior,
object,
pending, depRel))
ok = false;
pfree(objDescription);
term_object_addresses(&mypending);
return ok;
}
/*
* Step 2: scan pg_depend records that link to this object, showing
* the things that depend on it. Recursively delete those things.
@ -268,18 +407,24 @@ recursiveDeletion(const ObjectAddress *object,
case DEPENDENCY_NORMAL:
if (behavior == DROP_RESTRICT)
{
elog(NOTICE, "%s depends on %s",
getObjectDescription(&otherObject),
objDescription);
ok = false;
/*
* We've found a restricted object (or at least one
* that's not deletable along this path). Log for later
* processing. (Note it's okay if the same object gets
* into mypending multiple times.)
*/
add_exact_object_address(&otherObject, &mypending);
}
else
{
elog(NOTICE, "Drop cascades to %s",
getObjectDescription(&otherObject));
if (!recursiveDeletion(&otherObject, behavior,
recursionLevel + 1, depRel))
ok = false;
if (!recursiveDeletion(&otherObject, behavior,
object,
&mypending, depRel))
ok = false;
}
break;
case DEPENDENCY_AUTO:
case DEPENDENCY_INTERNAL:
@ -292,7 +437,8 @@ recursiveDeletion(const ObjectAddress *object,
getObjectDescription(&otherObject));
if (!recursiveDeletion(&otherObject, behavior,
recursionLevel + 1, depRel))
object,
&mypending, depRel))
ok = false;
break;
case DEPENDENCY_PIN:
@ -312,6 +458,36 @@ recursiveDeletion(const ObjectAddress *object,
systable_endscan(scan);
/*
* If we found no restricted objects, or got rid of them all via other
* paths, we're in good shape. Otherwise continue step 2 by processing
* the remaining restricted objects.
*/
if (mypending.numrefs > 0)
{
/*
* Successively extract and delete each remaining object.
* Note that the right things will happen if some of these objects
* depend on others: we'll report/delete each one exactly once.
*/
while (mypending.numrefs > 0)
{
ObjectAddress otherObject = mypending.refs[0];
del_object_address_by_index(0, &mypending);
elog(NOTICE, "%s depends on %s",
getObjectDescription(&otherObject),
objDescription);
if (!recursiveDeletion(&otherObject, behavior,
object,
&mypending, depRel))
ok = false;
}
ok = false;
}
/*
* We do not need CommandCounterIncrement here, since if step 2 did
* anything then each recursive call will have ended with one.
@ -328,6 +504,11 @@ recursiveDeletion(const ObjectAddress *object,
*/
DeleteComments(object->objectId, object->classId, object->objectSubId);
/*
* If this object is mentioned in any caller's pending list, remove it.
*/
del_object_address(object, pending);
/*
* CommandCounterIncrement here to ensure that preceding changes
* are all visible.
@ -338,6 +519,7 @@ recursiveDeletion(const ObjectAddress *object,
* And we're done!
*/
pfree(objDescription);
term_object_addresses(&mypending);
return ok;
}
@ -421,6 +603,387 @@ doDeletion(const ObjectAddress *object)
}
}
/*
* recordDependencyOnExpr - find expression dependencies
*
* This is used to find the dependencies of rules, constraint expressions,
* etc.
*
* Given an expression or query in node-tree form, find all the objects
* it refers to (tables, columns, operators, functions, etc). Record
* a dependency of the specified type from the given depender object
* to each object mentioned in the expression.
*
* rtable is the rangetable to be used to interpret Vars with varlevelsup=0.
* It can be NIL if no such variables are expected.
*
* XXX is it important to create dependencies on the datatypes mentioned in
* the expression? In most cases this would be redundant (eg, a ref to an
* operator indirectly references its input and output datatypes), but I'm
* not quite convinced there are no cases where we need it.
*/
void
recordDependencyOnExpr(const ObjectAddress *depender,
Node *expr, List *rtable,
DependencyType behavior)
{
find_expr_references_context context;
init_object_addresses(&context.addrs);
/* Set up interpretation for Vars at varlevelsup = 0 */
context.rtables = makeList1(rtable);
/* Scan the expression tree for referenceable objects */
find_expr_references_walker(expr, &context);
/* Remove any duplicates */
eliminate_duplicate_dependencies(&context.addrs);
/* And record 'em */
recordMultipleDependencies(depender,
context.addrs.refs, context.addrs.numrefs,
behavior);
term_object_addresses(&context.addrs);
}
/*
* Recursively search an expression tree for object references.
*/
static bool
find_expr_references_walker(Node *node,
find_expr_references_context *context)
{
if (node == NULL)
return false;
if (IsA(node, Var))
{
Var *var = (Var *) node;
int levelsup;
List *rtable,
*rtables;
RangeTblEntry *rte;
/* Find matching rtable entry, or complain if not found */
levelsup = var->varlevelsup;
rtables = context->rtables;
while (levelsup--)
{
if (rtables == NIL)
break;
rtables = lnext(rtables);
}
if (rtables == NIL)
elog(ERROR, "find_expr_references_walker: bogus varlevelsup %d",
var->varlevelsup);
rtable = lfirst(rtables);
if (var->varno <= 0 || var->varno > length(rtable))
elog(ERROR, "find_expr_references_walker: bogus varno %d",
var->varno);
rte = rt_fetch(var->varno, rtable);
/* If it's a plain relation, reference this column */
if (rte->rtekind == RTE_RELATION)
add_object_address(OCLASS_CLASS, rte->relid, var->varattno,
&context->addrs);
return false;
}
if (IsA(node, Expr))
{
Expr *expr = (Expr *) node;
if (expr->opType == OP_EXPR)
{
Oper *oper = (Oper *) expr->oper;
add_object_address(OCLASS_OPERATOR, oper->opno, 0,
&context->addrs);
}
else if (expr->opType == FUNC_EXPR)
{
Func *func = (Func *) expr->oper;
add_object_address(OCLASS_PROC, func->funcid, 0,
&context->addrs);
}
/* fall through to examine arguments */
}
if (IsA(node, Aggref))
{
Aggref *aggref = (Aggref *) node;
add_object_address(OCLASS_PROC, aggref->aggfnoid, 0,
&context->addrs);
/* fall through to examine arguments */
}
if (is_subplan(node))
{
/* Extra work needed here if we ever need this case */
elog(ERROR, "find_expr_references_walker: already-planned subqueries not supported");
}
if (IsA(node, Query))
{
/* Recurse into RTE subquery or not-yet-planned sublink subquery */
Query *query = (Query *) node;
List *rtable;
bool result;
/*
* Add whole-relation refs for each plain relation mentioned in the
* subquery's rtable. (Note: query_tree_walker takes care of
* recursing into RTE_FUNCTION and RTE_SUBQUERY RTEs, so no need
* to do that here.)
*/
foreach(rtable, query->rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rtable);
if (rte->rtekind == RTE_RELATION)
add_object_address(OCLASS_CLASS, rte->relid, 0,
&context->addrs);
}
/* Examine substructure of query */
context->rtables = lcons(query->rtable, context->rtables);
result = query_tree_walker(query,
find_expr_references_walker,
(void *) context, true);
context->rtables = lnext(context->rtables);
return result;
}
return expression_tree_walker(node, find_expr_references_walker,
(void *) context);
}
/*
* Given an array of dependency references, eliminate any duplicates.
*/
static void
eliminate_duplicate_dependencies(ObjectAddresses *addrs)
{
ObjectAddress *priorobj;
int oldref,
newrefs;
if (addrs->numrefs <= 1)
return; /* nothing to do */
/* Sort the refs so that duplicates are adjacent */
qsort((void *) addrs->refs, addrs->numrefs, sizeof(ObjectAddress),
object_address_comparator);
/* Remove dups */
priorobj = addrs->refs;
newrefs = 1;
for (oldref = 1; oldref < addrs->numrefs; oldref++)
{
ObjectAddress *thisobj = addrs->refs + oldref;
if (priorobj->classId == thisobj->classId &&
priorobj->objectId == thisobj->objectId)
{
if (priorobj->objectSubId == thisobj->objectSubId)
continue; /* identical, so drop thisobj */
/*
* If we have a whole-object reference and a reference to a
* part of the same object, we don't need the whole-object
* reference (for example, we don't need to reference both
* table foo and column foo.bar). The whole-object reference
* will always appear first in the sorted list.
*/
if (priorobj->objectSubId == 0)
{
/* replace whole ref with partial */
priorobj->objectSubId = thisobj->objectSubId;
continue;
}
}
/* Not identical, so add thisobj to output set */
priorobj++;
priorobj->classId = thisobj->classId;
priorobj->objectId = thisobj->objectId;
priorobj->objectSubId = thisobj->objectSubId;
newrefs++;
}
addrs->numrefs = newrefs;
}
/*
* qsort comparator for ObjectAddress items
*/
static int
object_address_comparator(const void *a, const void *b)
{
const ObjectAddress *obja = (const ObjectAddress *) a;
const ObjectAddress *objb = (const ObjectAddress *) b;
if (obja->classId < objb->classId)
return -1;
if (obja->classId > objb->classId)
return 1;
if (obja->objectId < objb->objectId)
return -1;
if (obja->objectId > objb->objectId)
return 1;
/*
* We sort the subId as an unsigned int so that 0 will come first.
* See logic in eliminate_duplicate_dependencies.
*/
if ((unsigned int) obja->objectSubId < (unsigned int) objb->objectSubId)
return -1;
if ((unsigned int) obja->objectSubId > (unsigned int) objb->objectSubId)
return 1;
return 0;
}
/*
* Routines for handling an expansible array of ObjectAddress items.
*
* init_object_addresses: initialize an ObjectAddresses array.
*/
static void
init_object_addresses(ObjectAddresses *addrs)
{
/* Initialize array to empty */
addrs->numrefs = 0;
addrs->maxrefs = 32; /* arbitrary initial array size */
addrs->refs = (ObjectAddress *)
palloc(addrs->maxrefs * sizeof(ObjectAddress));
addrs->link = NULL;
/* Initialize object_classes[] if not done yet */
/* This will be needed by add_object_address() */
if (!object_classes_initialized)
init_object_classes();
}
/*
* Add an entry to an ObjectAddresses array.
*
* It is convenient to specify the class by ObjectClass rather than directly
* by catalog OID.
*/
static void
add_object_address(ObjectClasses oclass, Oid objectId, int32 subId,
ObjectAddresses *addrs)
{
ObjectAddress *item;
/* enlarge array if needed */
if (addrs->numrefs >= addrs->maxrefs)
{
addrs->maxrefs *= 2;
addrs->refs = (ObjectAddress *)
repalloc(addrs->refs, addrs->maxrefs * sizeof(ObjectAddress));
}
/* record this item */
item = addrs->refs + addrs->numrefs;
item->classId = object_classes[oclass];
item->objectId = objectId;
item->objectSubId = subId;
addrs->numrefs++;
}
/*
* Add an entry to an ObjectAddresses array.
*
* As above, but specify entry exactly.
*/
static void
add_exact_object_address(const ObjectAddress *object,
ObjectAddresses *addrs)
{
ObjectAddress *item;
/* enlarge array if needed */
if (addrs->numrefs >= addrs->maxrefs)
{
addrs->maxrefs *= 2;
addrs->refs = (ObjectAddress *)
repalloc(addrs->refs, addrs->maxrefs * sizeof(ObjectAddress));
}
/* record this item */
item = addrs->refs + addrs->numrefs;
*item = *object;
addrs->numrefs++;
}
/*
* If an ObjectAddresses array contains any matches for the given object,
* remove it/them. Also, do the same in any linked ObjectAddresses arrays.
*/
static void
del_object_address(const ObjectAddress *object,
ObjectAddresses *addrs)
{
for (; addrs != NULL; addrs = addrs->link)
{
int i;
/* Scan backwards to simplify deletion logic. */
for (i = addrs->numrefs-1; i >= 0; i--)
{
ObjectAddress *thisobj = addrs->refs + i;
if (object->classId == thisobj->classId &&
object->objectId == thisobj->objectId)
{
/*
* Delete if exact match, or if thisobj is a subobject of
* the passed-in object.
*/
if (object->objectSubId == thisobj->objectSubId ||
object->objectSubId == 0)
del_object_address_by_index(i, addrs);
}
}
}
}
/*
* Remove an entry (specified by array index) from an ObjectAddresses array.
* The end item in the list is moved down to fill the hole.
*/
static void
del_object_address_by_index(int index, ObjectAddresses *addrs)
{
Assert(index >= 0 && index < addrs->numrefs);
addrs->refs[index] = addrs->refs[addrs->numrefs - 1];
addrs->numrefs--;
}
/*
* Clean up when done with an ObjectAddresses array.
*/
static void
term_object_addresses(ObjectAddresses *addrs)
{
pfree(addrs->refs);
}
/*
* Initialize the object_classes[] table.
*
* Although some of these OIDs aren't compile-time constants, they surely
* shouldn't change during a backend's run. So, we look them up the
* first time through and then cache them.
*/
static void
init_object_classes(void)
{
object_classes[OCLASS_CLASS] = RelOid_pg_class;
object_classes[OCLASS_PROC] = RelOid_pg_proc;
object_classes[OCLASS_TYPE] = RelOid_pg_type;
object_classes[OCLASS_CONSTRAINT] = get_system_catalog_relid(ConstraintRelationName);
object_classes[OCLASS_DEFAULT] = get_system_catalog_relid(AttrDefaultRelationName);
object_classes[OCLASS_LANGUAGE] = get_system_catalog_relid(LanguageRelationName);
object_classes[OCLASS_OPERATOR] = get_system_catalog_relid(OperatorRelationName);
object_classes[OCLASS_REWRITE] = get_system_catalog_relid(RewriteRelationName);
object_classes[OCLASS_TRIGGER] = get_system_catalog_relid(TriggerRelationName);
object_classes_initialized = true;
}
/*
* Determine the class of a given object identified by objectAddress.
*
@ -430,14 +993,6 @@ doDeletion(const ObjectAddress *object)
static ObjectClasses
getObjectClass(const ObjectAddress *object)
{
static bool reloids_initialized = false;
static Oid reloid_pg_constraint;
static Oid reloid_pg_attrdef;
static Oid reloid_pg_language;
static Oid reloid_pg_operator;
static Oid reloid_pg_rewrite;
static Oid reloid_pg_trigger;
/* Easy for the bootstrapped catalogs... */
switch (object->classId)
{
@ -456,48 +1011,36 @@ getObjectClass(const ObjectAddress *object)
/*
* Handle cases where catalog's OID is not hardwired.
*
* Although these OIDs aren't compile-time constants, they surely
* shouldn't change during a backend's run. So, look them up the
* first time through and then cache them.
*/
if (!reloids_initialized)
{
reloid_pg_constraint = get_system_catalog_relid(ConstraintRelationName);
reloid_pg_attrdef = get_system_catalog_relid(AttrDefaultRelationName);
reloid_pg_language = get_system_catalog_relid(LanguageRelationName);
reloid_pg_operator = get_system_catalog_relid(OperatorRelationName);
reloid_pg_rewrite = get_system_catalog_relid(RewriteRelationName);
reloid_pg_trigger = get_system_catalog_relid(TriggerRelationName);
reloids_initialized = true;
}
if (!object_classes_initialized)
init_object_classes();
if (object->classId == reloid_pg_constraint)
if (object->classId == object_classes[OCLASS_CONSTRAINT])
{
Assert(object->objectSubId == 0);
return OCLASS_CONSTRAINT;
}
if (object->classId == reloid_pg_attrdef)
if (object->classId == object_classes[OCLASS_DEFAULT])
{
Assert(object->objectSubId == 0);
return OCLASS_DEFAULT;
}
if (object->classId == reloid_pg_language)
if (object->classId == object_classes[OCLASS_LANGUAGE])
{
Assert(object->objectSubId == 0);
return OCLASS_LANGUAGE;
}
if (object->classId == reloid_pg_operator)
if (object->classId == object_classes[OCLASS_OPERATOR])
{
Assert(object->objectSubId == 0);
return OCLASS_OPERATOR;
}
if (object->classId == reloid_pg_rewrite)
if (object->classId == object_classes[OCLASS_REWRITE])
{
Assert(object->objectSubId == 0);
return OCLASS_REWRITE;
}
if (object->classId == reloid_pg_trigger)
if (object->classId == object_classes[OCLASS_TRIGGER])
{
Assert(object->objectSubId == 0);
return OCLASS_TRIGGER;

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.207 2002/07/15 16:33:31 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.208 2002/07/16 05:53:33 tgl Exp $
*
*
* INTERFACE ROUTINES
@ -1157,10 +1157,15 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, char *adbin)
colobject.objectSubId = attnum;
recordDependencyOn(&defobject, &colobject, DEPENDENCY_AUTO);
/*
* Record dependencies on objects used in the expression, too.
*/
recordDependencyOnExpr(&defobject, expr, NIL, DEPENDENCY_NORMAL);
}
/*
* Store a constraint expression for the given relation.
* Store a check-constraint expression for the given relation.
* The expression must be presented as a nodeToString() string.
*
* Caller is responsible for updating the count of constraints
@ -1191,6 +1196,10 @@ StoreRelCheck(Relation rel, char *ccname, char *ccbin)
/*
* Find columns of rel that are used in ccbin
*
* NB: pull_var_clause is okay here only because we don't allow
* subselects in check constraints; it would fail to examine the
* contents of subselects.
*/
varList = pull_var_clause(expr, false);
keycount = length(varList);
@ -1226,8 +1235,8 @@ StoreRelCheck(Relation rel, char *ccname, char *ccbin)
false, /* Is Deferrable */
false, /* Is Deferred */
RelationGetRelid(rel), /* relation */
attNos, /* List of attributes in the constraint */
keycount, /* # attributes in the constraint */
attNos, /* attrs in the constraint */
keycount, /* # attrs in the constraint */
InvalidOid, /* not a domain constraint */
InvalidOid, /* Foreign key fields */
NULL,
@ -1235,6 +1244,7 @@ StoreRelCheck(Relation rel, char *ccname, char *ccbin)
' ',
' ',
' ',
expr, /* Tree form check constraint */
ccbin, /* Binary form check constraint */
ccsrc); /* Source form check constraint */

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.183 2002/07/14 21:08:08 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.184 2002/07/16 05:53:33 tgl Exp $
*
*
* INTERFACE ROUTINES
@ -712,7 +712,8 @@ index_create(Oid heapRelationId,
' ',
' ',
' ',
NULL, /* Constraint Bin & Src */
NULL, /* no check constraint */
NULL,
NULL);
referenced.classId = get_system_catalog_relid(ConstraintRelationName);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/catalog/pg_constraint.c,v 1.1 2002/07/12 18:43:15 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/catalog/pg_constraint.c,v 1.2 2002/07/16 05:53:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -52,6 +52,7 @@ CreateConstraintEntry(const char *constraintName,
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
Node *conExpr,
const char *conBin,
const char *conSrc)
{
@ -227,6 +228,24 @@ CreateConstraintEntry(const char *constraintName,
}
}
if (conExpr != NULL)
{
/*
* Register dependencies from constraint to objects mentioned
* in CHECK expression. We gin up a rather bogus rangetable
* list to handle any Vars in the constraint.
*/
RangeTblEntry rte;
MemSet(&rte, 0, sizeof(rte));
rte.type = T_RangeTblEntry;
rte.rtekind = RTE_RELATION;
rte.relid = relId;
recordDependencyOnExpr(&conobject, conExpr, makeList1(&rte),
DEPENDENCY_NORMAL);
}
return conOid;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/catalog/pg_depend.c,v 1.1 2002/07/12 18:43:15 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/catalog/pg_depend.c,v 1.2 2002/07/16 05:53:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -38,6 +38,19 @@ void
recordDependencyOn(const ObjectAddress *depender,
const ObjectAddress *referenced,
DependencyType behavior)
{
recordMultipleDependencies(depender, referenced, 1, behavior);
}
/*
* Record multiple dependencies (of the same kind) for a single dependent
* object. This has a little less overhead than recording each separately.
*/
void
recordMultipleDependencies(const ObjectAddress *depender,
const ObjectAddress *referenced,
int nreferenced,
DependencyType behavior)
{
Relation dependDesc;
HeapTuple tup;
@ -45,6 +58,10 @@ recordDependencyOn(const ObjectAddress *depender,
char nulls[Natts_pg_depend];
Datum values[Natts_pg_depend];
Relation idescs[Num_pg_depend_indices];
bool indices_opened = false;
if (nreferenced <= 0)
return; /* nothing to do */
/*
* During bootstrap, do nothing since pg_depend may not exist yet.
@ -55,45 +72,52 @@ recordDependencyOn(const ObjectAddress *depender,
dependDesc = heap_openr(DependRelationName, RowExclusiveLock);
/*
* If the referenced object is pinned by the system, there's no real
* need to record dependencies on it. This saves lots of space in
* pg_depend, so it's worth the time taken to check.
*/
if (!isObjectPinned(referenced, dependDesc))
memset(nulls, ' ', sizeof(nulls));
for (i = 0; i < nreferenced; i++, referenced++)
{
/*
* Record the Dependency. Note we don't bother to check for
* duplicate dependencies; there's no harm in them.
* If the referenced object is pinned by the system, there's no real
* need to record dependencies on it. This saves lots of space in
* pg_depend, so it's worth the time taken to check.
*/
for (i = 0; i < Natts_pg_depend; ++i)
if (!isObjectPinned(referenced, dependDesc))
{
nulls[i] = ' ';
values[i] = (Datum) 0;
/*
* Record the Dependency. Note we don't bother to check for
* duplicate dependencies; there's no harm in them.
*/
values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
values[Anum_pg_depend_deptype -1] = CharGetDatum((char) behavior);
tup = heap_formtuple(dependDesc->rd_att, values, nulls);
simple_heap_insert(dependDesc, tup);
/*
* Keep indices current
*/
if (!indices_opened)
{
CatalogOpenIndices(Num_pg_depend_indices, Name_pg_depend_indices, idescs);
indices_opened = true;
}
CatalogIndexInsert(idescs, Num_pg_depend_indices, dependDesc, tup);
heap_freetuple(tup);
}
values[Anum_pg_depend_classid - 1] = ObjectIdGetDatum(depender->classId);
values[Anum_pg_depend_objid - 1] = ObjectIdGetDatum(depender->objectId);
values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(depender->objectSubId);
values[Anum_pg_depend_refclassid - 1] = ObjectIdGetDatum(referenced->classId);
values[Anum_pg_depend_refobjid - 1] = ObjectIdGetDatum(referenced->objectId);
values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(referenced->objectSubId);
values[Anum_pg_depend_deptype -1] = CharGetDatum((char) behavior);
tup = heap_formtuple(dependDesc->rd_att, values, nulls);
simple_heap_insert(dependDesc, tup);
/*
* Keep indices current
*/
CatalogOpenIndices(Num_pg_depend_indices, Name_pg_depend_indices, idescs);
CatalogIndexInsert(idescs, Num_pg_depend_indices, dependDesc, tup);
CatalogCloseIndices(Num_pg_depend_indices, idescs);
}
if (indices_opened)
CatalogCloseIndices(Num_pg_depend_indices, idescs);
heap_close(dependDesc, RowExclusiveLock);
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.21 2002/07/15 16:33:31 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.22 2002/07/16 05:53:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -2708,6 +2708,7 @@ createForeignKeyConstraint(Relation rel, Relation pkrel,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
fkconstraint->fk_matchtype,
NULL, /* no check constraint */
NULL,
NULL);
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.74 2002/07/12 18:43:17 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.75 2002/07/16 05:53:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -47,9 +47,11 @@ InsertRule(char *rulname,
Oid eventrel_oid,
AttrNumber evslot_index,
bool evinstead,
char *evqual,
char *actiontree)
Node *event_qual,
List *action)
{
char *evqual = nodeToString(event_qual);
char *actiontree = nodeToString((Node *) action);
int i;
Datum values[Natts_pg_rewrite];
char nulls[Natts_pg_rewrite];
@ -123,6 +125,21 @@ InsertRule(char *rulname,
recordDependencyOn(&myself, &referenced,
(evtype == CMD_SELECT) ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
/*
* Also install dependencies on objects referenced in action and qual.
*/
recordDependencyOnExpr(&myself, (Node *) action, NIL,
DEPENDENCY_NORMAL);
if (event_qual != NULL)
{
/* Find query containing OLD/NEW rtable entries */
Query *qry = (Query *) lfirst(action);
qry = getInsertSelectQuery(qry, NULL);
recordDependencyOnExpr(&myself, event_qual, qry->rtable,
DEPENDENCY_NORMAL);
}
heap_close(pg_rewrite_desc, RowExclusiveLock);
return rewriteObjectId;
@ -141,8 +158,6 @@ DefineQueryRewrite(RuleStmt *stmt)
Oid ruleId;
int event_attno;
Oid event_attype;
char *actionP,
*event_qualP;
List *l;
Query *query;
AclResult aclresult;
@ -342,16 +357,13 @@ DefineQueryRewrite(RuleStmt *stmt)
/* discard rule if it's null action and not INSTEAD; it's a no-op */
if (action != NIL || is_instead)
{
event_qualP = nodeToString(event_qual);
actionP = nodeToString(action);
ruleId = InsertRule(stmt->rulename,
event_type,
ev_relid,
event_attno,
is_instead,
event_qualP,
actionP);
event_qual,
action);
/*
* Set pg_class 'relhasrules' field TRUE for event relation. If

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: dependency.h,v 1.1 2002/07/12 18:43:19 tgl Exp $
* $Id: dependency.h,v 1.2 2002/07/16 05:53:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -83,10 +83,19 @@ typedef struct ObjectAddress
extern void performDeletion(const ObjectAddress *object,
DropBehavior behavior);
extern void recordDependencyOnExpr(const ObjectAddress *depender,
Node *expr, List *rtable,
DependencyType behavior);
/* in pg_depend.c */
extern void recordDependencyOn(const ObjectAddress *depender,
const ObjectAddress *referenced,
DependencyType behavior);
extern void recordMultipleDependencies(const ObjectAddress *depender,
const ObjectAddress *referenced,
int nreferenced,
DependencyType behavior);
#endif /* DEPENDENCY_H */

View File

@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: pg_constraint.h,v 1.1 2002/07/12 18:43:19 tgl Exp $
* $Id: pg_constraint.h,v 1.2 2002/07/16 05:53:34 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
@ -158,6 +158,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
Node *conExpr,
const char *conBin,
const char *conSrc);

View File

@ -544,13 +544,19 @@ from (select oid from pg_class where relname = 'atest1') as t1;
\c regression
DROP FUNCTION testfunc2(int);
DROP FUNCTION testfunc4(boolean);
DROP VIEW atestv1;
DROP VIEW atestv2;
-- this should cascade to drop atestv4
DROP VIEW atestv3 CASCADE;
NOTICE: Drop cascades to rule _RETURN on view atestv3
NOTICE: Drop cascades to rule _RETURN on view atestv4
NOTICE: Drop cascades to view atestv4
-- this should complain "does not exist"
DROP VIEW atestv4;
ERROR: view "atestv4" does not exist
DROP TABLE atest1;
DROP TABLE atest2;
DROP TABLE atest3;
DROP VIEW atestv1;
DROP VIEW atestv2;
DROP VIEW atestv3;
DROP VIEW atestv4;
DROP GROUP regressgroup1;
DROP GROUP regressgroup2;
DROP USER regressuser1;

View File

@ -61,6 +61,7 @@ SELECT * FROM vw_getfoo;
(1 row)
-- sql, proretset = t, prorettype = b
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
CREATE FUNCTION getfoo(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL;
SELECT * FROM getfoo(1) AS t1;
@ -70,7 +71,6 @@ SELECT * FROM getfoo(1) AS t1;
1
(2 rows)
DROP VIEW vw_getfoo;
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;
getfoo
@ -80,6 +80,7 @@ SELECT * FROM vw_getfoo;
(2 rows)
-- sql, proretset = t, prorettype = b
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
CREATE FUNCTION getfoo(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL;
SELECT * FROM getfoo(1) AS t1;
@ -89,7 +90,6 @@ SELECT * FROM getfoo(1) AS t1;
Ed
(2 rows)
DROP VIEW vw_getfoo;
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;
getfoo
@ -99,6 +99,7 @@ SELECT * FROM vw_getfoo;
(2 rows)
-- sql, proretset = f, prorettype = c
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
CREATE FUNCTION getfoo(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
SELECT * FROM getfoo(1) AS t1;
@ -107,7 +108,6 @@ SELECT * FROM getfoo(1) AS t1;
1 | 1 | Joe
(1 row)
DROP VIEW vw_getfoo;
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;
fooid | foosubid | fooname
@ -116,6 +116,7 @@ SELECT * FROM vw_getfoo;
(1 row)
-- sql, proretset = t, prorettype = c
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
CREATE FUNCTION getfoo(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
SELECT * FROM getfoo(1) AS t1;
@ -125,7 +126,6 @@ SELECT * FROM getfoo(1) AS t1;
1 | 2 | Ed
(2 rows)
DROP VIEW vw_getfoo;
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;
fooid | foosubid | fooname
@ -135,6 +135,7 @@ SELECT * FROM vw_getfoo;
(2 rows)
-- plpgsql, proretset = f, prorettype = b
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
CREATE FUNCTION getfoo(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE 'plpgsql';
SELECT * FROM getfoo(1) AS t1;
@ -143,7 +144,6 @@ SELECT * FROM getfoo(1) AS t1;
1
(1 row)
DROP VIEW vw_getfoo;
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;
getfoo
@ -152,6 +152,7 @@ SELECT * FROM vw_getfoo;
(1 row)
-- plpgsql, proretset = f, prorettype = c
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
CREATE FUNCTION getfoo(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE 'plpgsql';
SELECT * FROM getfoo(1) AS t1;
@ -160,7 +161,6 @@ SELECT * FROM getfoo(1) AS t1;
1 | 1 | Joe
(1 row)
DROP VIEW vw_getfoo;
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;
fooid | foosubid | fooname
@ -168,11 +168,11 @@ SELECT * FROM vw_getfoo;
1 | 1 | Joe
(1 row)
DROP TABLE foo2;
DROP FUNCTION foot(int);
DROP TABLE foo;
DROP FUNCTION getfoo(int);
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
DROP FUNCTION foot(int);
DROP TABLE foo2;
DROP TABLE foo;
-- Rescan tests --
CREATE TABLE foorescan (fooid int, foosubid int, fooname text, primary key(fooid,foosubid));
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index 'foorescan_pkey' for table 'foorescan'
@ -339,10 +339,10 @@ SELECT * FROM fooview2 AS fv WHERE fv.maxsubid = 5;
5008 | 5
(6 rows)
DROP TABLE foorescan;
DROP FUNCTION foorescan(int,int);
DROP VIEW vw_foorescan;
DROP TABLE barrescan;
DROP FUNCTION foorescan(int);
DROP VIEW fooview1;
DROP VIEW fooview2;
DROP FUNCTION foorescan(int,int);
DROP FUNCTION foorescan(int);
DROP TABLE foorescan;
DROP TABLE barrescan;

View File

@ -292,15 +292,17 @@ from (select oid from pg_class where relname = 'atest1') as t1;
DROP FUNCTION testfunc2(int);
DROP FUNCTION testfunc4(boolean);
DROP VIEW atestv1;
DROP VIEW atestv2;
-- this should cascade to drop atestv4
DROP VIEW atestv3 CASCADE;
-- this should complain "does not exist"
DROP VIEW atestv4;
DROP TABLE atest1;
DROP TABLE atest2;
DROP TABLE atest3;
DROP VIEW atestv1;
DROP VIEW atestv2;
DROP VIEW atestv3;
DROP VIEW atestv4;
DROP GROUP regressgroup1;
DROP GROUP regressgroup2;

View File

@ -32,58 +32,58 @@ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;
-- sql, proretset = t, prorettype = b
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
CREATE FUNCTION getfoo(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL;
SELECT * FROM getfoo(1) AS t1;
DROP VIEW vw_getfoo;
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;
-- sql, proretset = t, prorettype = b
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
CREATE FUNCTION getfoo(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL;
SELECT * FROM getfoo(1) AS t1;
DROP VIEW vw_getfoo;
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;
-- sql, proretset = f, prorettype = c
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
CREATE FUNCTION getfoo(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
SELECT * FROM getfoo(1) AS t1;
DROP VIEW vw_getfoo;
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;
-- sql, proretset = t, prorettype = c
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
CREATE FUNCTION getfoo(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
SELECT * FROM getfoo(1) AS t1;
DROP VIEW vw_getfoo;
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;
-- plpgsql, proretset = f, prorettype = b
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
CREATE FUNCTION getfoo(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE 'plpgsql';
SELECT * FROM getfoo(1) AS t1;
DROP VIEW vw_getfoo;
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;
-- plpgsql, proretset = f, prorettype = c
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
CREATE FUNCTION getfoo(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE 'plpgsql';
SELECT * FROM getfoo(1) AS t1;
DROP VIEW vw_getfoo;
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
SELECT * FROM vw_getfoo;
DROP TABLE foo2;
DROP FUNCTION foot(int);
DROP TABLE foo;
DROP FUNCTION getfoo(int);
DROP VIEW vw_getfoo;
DROP FUNCTION getfoo(int);
DROP FUNCTION foot(int);
DROP TABLE foo2;
DROP TABLE foo;
-- Rescan tests --
CREATE TABLE foorescan (fooid int, foosubid int, fooname text, primary key(fooid,foosubid));
@ -172,10 +172,10 @@ SELECT * FROM fooview1 AS fv WHERE fv.fooid = 5004;
CREATE VIEW fooview2 AS SELECT b.fooid, max(f.foosubid) AS maxsubid FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) GROUP BY b.fooid ORDER BY 1,2;
SELECT * FROM fooview2 AS fv WHERE fv.maxsubid = 5;
DROP TABLE foorescan;
DROP FUNCTION foorescan(int,int);
DROP VIEW vw_foorescan;
DROP TABLE barrescan;
DROP FUNCTION foorescan(int);
DROP VIEW fooview1;
DROP VIEW fooview2;
DROP FUNCTION foorescan(int,int);
DROP FUNCTION foorescan(int);
DROP TABLE foorescan;
DROP TABLE barrescan;