OSDN Git Service

Fix permission-checking bug reported by Tim Burgess 10-Feb-03 (this time
[pg-rex/syncrep.git] / src / backend / commands / view.c
index c71ee85..a8c3cb5 100644 (file)
  * view.c
  *       use rewrite rules to construct views
  *
- * Copyright (c) 1994, Regents of the University of California
+ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
  *
- *     $Id: view.c,v 1.35 1999/07/15 23:03:08 momjian Exp $
+ *
+ * IDENTIFICATION
+ *       $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.81 2004/01/14 23:01:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
-#include <stdio.h>
-#include <string.h>
-
 #include "postgres.h"
 
-#include "catalog/heap.h"
-#include "access/xact.h"
+#include "access/heapam.h"
+#include "catalog/dependency.h"
+#include "catalog/namespace.h"
+#include "commands/tablecmds.h"
+#include "commands/view.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
-#include "parser/parse_type.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
-#include "rewrite/rewriteRemove.h"
-#include "commands/creatinh.h"
-#include "commands/view.h"
+#include "rewrite/rewriteSupport.h"
+#include "utils/acl.h"
+#include "utils/lsyscache.h"
+
+
+static void checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc);
+
 
 /*---------------------------------------------------------------------
  * DefineVirtualRelation
  *
- * Create the "view" relation.
- * `DefineRelation' does all the work, we just provide the correct
- * arguments!
- *
- * If the relation already exists, then 'DefineRelation' will abort
- * the xact...
+ * Create the "view" relation. `DefineRelation' does all the work,
+ * we just provide the correct arguments ... at least when we're
+ * creating a view.  If we're updating an existing view, we have to
+ * work harder.
  *---------------------------------------------------------------------
  */
-static void
-DefineVirtualRelation(char *relname, List *tlist)
+static Oid
+DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
 {
-       CreateStmt      createStmt;
+       Oid                     viewOid,
+                               namespaceId;
+       CreateStmt *createStmt = makeNode(CreateStmt);
        List       *attrList,
                           *t;
-       TargetEntry *entry;
-       Resdom     *res;
-       char       *resname;
-       char       *restypename;
 
        /*
-        * create a list with one entry per attribute of this relation. Each
-        * entry is a two element list. The first element is the name of the
-        * attribute (a string) and the second the name of the type (NOTE: a
-        * string, not a type id!).
+        * create a list of ColumnDef nodes based on the names and types of
+        * the (non-junk) targetlist items from the view's SELECT list.
         */
        attrList = NIL;
-       if (tlist != NIL)
+       foreach(t, tlist)
        {
-               foreach(t, tlist)
+               TargetEntry *entry = lfirst(t);
+               Resdom     *res = entry->resdom;
+
+               if (!res->resjunk)
                {
                        ColumnDef  *def = makeNode(ColumnDef);
-                       TypeName   *typename;
-
-                       /*
-                        * find the names of the attribute & its type
-                        */
-                       entry = lfirst(t);
-                       res = entry->resdom;
-                       resname = res->resname;
-                       restypename = typeidTypeName(res->restype);
+                       TypeName   *typename = makeNode(TypeName);
 
-                       typename = makeNode(TypeName);
+                       def->colname = pstrdup(res->resname);
 
-                       typename->name = pstrdup(restypename);
+                       typename->typeid = res->restype;
                        typename->typmod = res->restypmod;
-
-                       def->colname = pstrdup(resname);
-
                        def->typename = typename;
 
+                       def->inhcount = 0;
+                       def->is_local = true;
                        def->is_not_null = false;
-                       def->defval = (char *) NULL;
+                       def->raw_default = NULL;
+                       def->cooked_default = NULL;
+                       def->constraints = NIL;
+                       def->support = NULL;
 
                        attrList = lappend(attrList, def);
                }
        }
-       else
-               elog(ERROR, "attempted to define virtual relation with no attrs");
 
-       /*
-        * now create the parametesr for keys/inheritance etc. All of them are
-        * nil...
-        */
-       createStmt.relname = relname;
-       createStmt.istemp = false;
-       createStmt.tableElts = attrList;
-/*       createStmt.tableType = NULL;*/
-       createStmt.inhRelnames = NIL;
-       createStmt.constraints = NIL;
+       if (attrList == NIL)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                errmsg("view must have at least one column")));
 
        /*
-        * finally create the relation...
+        * Check to see if we want to replace an existing view.
         */
-       DefineRelation(&createStmt, RELKIND_RELATION);
+       namespaceId = RangeVarGetCreationNamespace(relation);
+       viewOid = get_relname_relid(relation->relname, namespaceId);
+
+       if (OidIsValid(viewOid) && replace)
+       {
+               Relation        rel;
+               TupleDesc       descriptor;
+
+               /*
+                * Yes.  Get exclusive lock on the existing view ...
+                */
+               rel = relation_open(viewOid, AccessExclusiveLock);
+
+               /*
+                * Make sure it *is* a view, and do permissions checks.
+                */
+               if (rel->rd_rel->relkind != RELKIND_VIEW)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                        errmsg("\"%s\" is not a view",
+                                                       RelationGetRelationName(rel))));
+
+               if (!pg_class_ownercheck(viewOid, GetUserId()))
+                       aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
+                                                  RelationGetRelationName(rel));
+
+               /*
+                * Create a tuple descriptor to compare against the existing view,
+                * and verify it matches.
+                */
+               descriptor = BuildDescForRelation(attrList);
+               checkViewTupleDesc(descriptor, rel->rd_att);
+
+               /*
+                * Seems okay, so return the OID of the pre-existing view.
+                */
+               relation_close(rel, NoLock);    /* keep the lock! */
+
+               return viewOid;
+       }
+       else
+       {
+               /*
+                * now create the parameters for keys/inheritance etc. All of them
+                * are nil...
+                */
+               createStmt->relation = (RangeVar *) relation;
+               createStmt->tableElts = attrList;
+               createStmt->inhRelations = NIL;
+               createStmt->constraints = NIL;
+               createStmt->hasoids = MUST_NOT_HAVE_OIDS;
+               createStmt->oncommit = ONCOMMIT_NOOP;
+
+               /*
+                * finally create the relation (this will error out if there's an
+                * existing view, so we don't need more code to complain if
+                * "replace" is false).
+                */
+               return DefineRelation(createStmt, RELKIND_VIEW);
+       }
 }
 
-/*------------------------------------------------------------------
- * makeViewRetrieveRuleName
- *
- * Given a view name, returns the name for the 'on retrieve to "view"'
- * rule.
- * This routine is called when defining/removing a view.
- *------------------------------------------------------------------
+/*
+ * Verify that tupledesc associated with proposed new view definition
+ * matches tupledesc of old view.  This is basically a cut-down version
+ * of equalTupleDescs(), with code added to generate specific complaints.
  */
-char *
-MakeRetrieveViewRuleName(char *viewName)
+static void
+checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc)
 {
-       char       *buf;
+       int                     i;
 
-       buf = palloc(strlen(viewName) + 5);
-       snprintf(buf, strlen(viewName) + 5, "_RET%s", viewName);
+       if (newdesc->natts != olddesc->natts)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                errmsg("cannot change number of columns in view")));
+       /* we can ignore tdhasoid */
+
+       for (i = 0; i < newdesc->natts; i++)
+       {
+               Form_pg_attribute newattr = newdesc->attrs[i];
+               Form_pg_attribute oldattr = olddesc->attrs[i];
+
+               /* XXX not right, but we don't support DROP COL on view anyway */
+               if (newattr->attisdropped != oldattr->attisdropped)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                        errmsg("cannot change number of columns in view")));
+
+               if (strcmp(NameStr(newattr->attname), NameStr(oldattr->attname)) != 0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                        errmsg("cannot change name of view column \"%s\"",
+                                                       NameStr(oldattr->attname))));
+               /* XXX would it be safe to allow atttypmod to change?  Not sure */
+               if (newattr->atttypid != oldattr->atttypid ||
+                       newattr->atttypmod != oldattr->atttypmod)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                  errmsg("cannot change data type of view column \"%s\"",
+                                                 NameStr(oldattr->attname))));
+               /* We can ignore the remaining attributes of an attribute... */
+       }
 
-       return buf;
+       /*
+        * We ignore the constraint fields.  The new view desc can't have any
+        * constraints, and the only ones that could be on the old view are
+        * defaults, which we are happy to leave in place.
+        */
 }
 
 static RuleStmt *
-FormViewRetrieveRule(char *viewName, Query *viewParse)
+FormViewRetrieveRule(const RangeVar *view, Query *viewParse, bool replace)
 {
        RuleStmt   *rule;
-       char       *rname;
-       Attr       *attr;
 
        /*
         * Create a RuleStmt that corresponds to the suitable rewrite rule
         * args for DefineQueryRewrite();
         */
        rule = makeNode(RuleStmt);
-       rname = MakeRetrieveViewRuleName(viewName);
-
-       attr = makeNode(Attr);
-       attr->relname = pstrdup(viewName);
-/*       attr->refname = pstrdup(viewName);*/
-       rule->rulename = pstrdup(rname);
+       rule->relation = copyObject((RangeVar *) view);
+       rule->rulename = pstrdup(ViewSelectRuleName);
        rule->whereClause = NULL;
        rule->event = CMD_SELECT;
-       rule->object = attr;
        rule->instead = true;
-       rule->actions = lcons(viewParse, NIL);
+       rule->actions = makeList1(viewParse);
+       rule->replace = replace;
 
        return rule;
 }
 
 static void
-DefineViewRules(char *viewName, Query *viewParse)
+DefineViewRules(const RangeVar *view, Query *viewParse, bool replace)
 {
-       RuleStmt   *retrieve_rule = NULL;
+       RuleStmt   *retrieve_rule;
 
 #ifdef NOTYET
-       RuleStmt   *replace_rule = NULL;
-       RuleStmt   *append_rule = NULL;
-       RuleStmt   *delete_rule = NULL;
-
+       RuleStmt   *replace_rule;
+       RuleStmt   *append_rule;
+       RuleStmt   *delete_rule;
 #endif
 
-       retrieve_rule = FormViewRetrieveRule(viewName, viewParse);
+       retrieve_rule = FormViewRetrieveRule(view, viewParse, replace);
 
 #ifdef NOTYET
-
-       replace_rule = FormViewReplaceRule(viewName, viewParse);
-       append_rule = FormViewAppendRule(viewName, viewParse);
-       delete_rule = FormViewDeleteRule(viewName, viewParse);
-
+       replace_rule = FormViewReplaceRule(view, viewParse);
+       append_rule = FormViewAppendRule(view, viewParse);
+       delete_rule = FormViewDeleteRule(view, viewParse);
 #endif
 
        DefineQueryRewrite(retrieve_rule);
@@ -188,55 +259,57 @@ DefineViewRules(char *viewName, Query *viewParse)
  * This update consists of adding two new entries IN THE BEGINNING
  * of the range table (otherwise the rule system will die a slow,
  * horrible and painful death, and we do not want that now, do we?)
- * one for the CURRENT relation and one for the NEW one (both of
+ * one for the OLD relation and one for the NEW one (both of
  * them refer in fact to the "view" relation).
  *
  * Of course we must also increase the 'varnos' of all the Var nodes
  * by 2...
  *
- * NOTE: these are destructive changes. It would be difficult to
- * make a complete copy of the parse tree and make the changes
- * in the copy.
+ * These extra RT entries are not actually used in the query,
+ * except for run-time permission checking.
  *---------------------------------------------------------------
  */
-static void
-UpdateRangeTableOfViewParse(char *viewName, Query *viewParse)
+static Query *
+UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 {
-       List       *old_rt;
        List       *new_rt;
        RangeTblEntry *rt_entry1,
                           *rt_entry2;
 
        /*
-        * first offset all var nodes by 2
+        * Make a copy of the given parsetree.  It's not so much that we don't
+        * want to scribble on our input, it's that the parser has a bad habit
+        * of outputting multiple links to the same subtree for constructs
+        * like BETWEEN, and we mustn't have OffsetVarNodes increment the
+        * varno of a Var node twice.  copyObject will expand any
+        * multiply-referenced subtree into multiple copies.
         */
-       OffsetVarNodes((Node *) viewParse->targetList, 2, 0);
-       OffsetVarNodes(viewParse->qual, 2, 0);
-
-       OffsetVarNodes(viewParse->havingQual, 2, 0);
-
+       viewParse = (Query *) copyObject(viewParse);
 
        /*
-        * find the old range table...
+        * Create the 2 new range table entries and form the new range
+        * table... OLD first, then NEW....
         */
-       old_rt = viewParse->rtable;
+       rt_entry1 = addRangeTableEntryForRelation(NULL, viewOid,
+                                                                                         makeAlias("*OLD*", NIL),
+                                                                                         false, false);
+       rt_entry2 = addRangeTableEntryForRelation(NULL, viewOid,
+                                                                                         makeAlias("*NEW*", NIL),
+                                                                                         false, false);
+       /* Must override addRangeTableEntry's default access-check flags */
+       rt_entry1->requiredPerms = 0;
+       rt_entry2->requiredPerms = 0;
+
+       new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
-       /*
-        * create the 2 new range table entries and form the new range
-        * table... CURRENT first, then NEW....
-        */
-       rt_entry1 = addRangeTableEntry(NULL, (char *) viewName, "*CURRENT*",
-                                                                  FALSE, FALSE);
-       rt_entry2 = addRangeTableEntry(NULL, (char *) viewName, "*NEW*",
-                                                                  FALSE, FALSE);
-       new_rt = lcons(rt_entry2, old_rt);
-       new_rt = lcons(rt_entry1, new_rt);
+       viewParse->rtable = new_rt;
 
        /*
-        * Now the tricky part.... Update the range table in place... Be
-        * careful here, or hell breaks loooooooooooooOOOOOOOOOOOOOOOOOOSE!
+        * Now offset all var nodes by 2, and jointree RT indexes too.
         */
-       viewParse->rtable = new_rt;
+       OffsetVarNodes((Node *) viewParse, 2, 0);
+
+       return viewParse;
 }
 
 /*-------------------------------------------------------------------
@@ -252,17 +325,18 @@ UpdateRangeTableOfViewParse(char *viewName, Query *viewParse)
  *-------------------------------------------------------------------
  */
 void
-DefineView(char *viewName, Query *viewParse)
+DefineView(const RangeVar *view, Query *viewParse, bool replace)
 {
-       List       *viewTlist;
-
-       viewTlist = viewParse->targetList;
+       Oid                     viewOid;
 
        /*
-        * Create the "view" relation NOTE: if it already exists, the xaxt
-        * will be aborted.
+        * Create the view relation
+        *
+        * NOTE: if it already exists and replace is false, the xact will be
+        * aborted.
         */
-       DefineVirtualRelation(viewName, viewTlist);
+
+       viewOid = DefineVirtualRelation(view, viewParse->targetList, replace);
 
        /*
         * The relation we have just created is not visible to any other
@@ -273,39 +347,35 @@ DefineView(char *viewName, Query *viewParse)
 
        /*
         * The range table of 'viewParse' does not contain entries for the
-        * "CURRENT" and "NEW" relations. So... add them! NOTE: we make the
-        * update in place! After this call 'viewParse' will never be what it
-        * used to be...
+        * "OLD" and "NEW" relations. So... add them!
         */
-       UpdateRangeTableOfViewParse(viewName, viewParse);
-       DefineViewRules(viewName, viewParse);
+       viewParse = UpdateRangeTableOfViewParse(viewOid, viewParse);
+
+       /*
+        * Now create the rules associated with the view.
+        */
+       DefineViewRules(view, viewParse, replace);
 }
 
-/*------------------------------------------------------------------
+/*
  * RemoveView
  *
  * Remove a view given its name
- *------------------------------------------------------------------
+ *
+ * We just have to drop the relation; the associated rules will be
+ * cleaned up automatically.
  */
 void
-RemoveView(char *viewName)
+RemoveView(const RangeVar *view, DropBehavior behavior)
 {
-       char       *rname;
+       Oid                     viewOid;
+       ObjectAddress object;
 
-       /*
-        * first remove all the "view" rules... Currently we only have one!
-        */
-       rname = MakeRetrieveViewRuleName(viewName);
-       RemoveRewriteRule(rname);
+       viewOid = RangeVarGetRelid(view, false);
 
-       /*
-        * we don't really need that, but just in case...
-        */
-       CommandCounterIncrement();
+       object.classId = RelOid_pg_class;
+       object.objectId = viewOid;
+       object.objectSubId = 0;
 
-       /*
-        * now remove the relation.
-        */
-       heap_destroy_with_catalog(viewName);
-       pfree(rname);
+       performDeletion(&object, behavior);
 }